[
  {
    "path": ".claude/PROJECT_GUIDE.md",
    "content": "# Configarr - AI Agent Quick Start Guide\n\n## Project Overview\n\n**Configarr** is a configuration and synchronization tool for \\*arr applications (Sonarr, Radarr, Lidarr, Readarr, Whisparr). It integrates with TRaSH Guides to automate custom formats, quality profiles, and other settings.\n\n- **Language**: TypeScript (Node.js)\n- **Package Manager**: **pnpm** (always use `pnpm`, never `npm` or `yarn`)\n- **Build Tool**: esbuild\n- **Test Framework**: Vitest\n- **Code Style**: Prettier\n\n## Quick Setup\n\n```bash\n# Install dependencies\npnpm install\n\n# Development\npnpm start              # Run the application\npnpm test              # Run tests\npnpm test:watch        # Run tests in watch mode\npnpm build             # Build for production\npnpm lint              # Check formatting\npnpm lint:fix          # Fix formatting\npnpm typecheck         # TypeScript type checking\n```\n\n## Development Rules\n\n### ✅ Must Do After Every Implementation\n\n1. **Run all three checks** - ALL must pass before considering work complete:\n   ```bash\n   pnpm build && pnpm test && pnpm lint\n   ```\n2. **Type checking** - Ensure no TypeScript errors:\n   ```bash\n   pnpm typecheck\n   ```\n\n### 🎯 Coding Standards\n\n1. **Follow Existing Patterns**\n   - Study similar existing code before implementing new features\n   - Maintain consistency with current architecture\n   - Use established patterns (e.g., rootFolder pattern for new modules)\n\n2. **TypeScript Best Practices**\n   - Use strict typing - avoid `any` when possible\n   - Prefer interfaces for public APIs, types for internal use\n   - Use type inference where it improves readability\n   - Leverage union types and discriminated unions\n   - Use `unknown` instead of `any` for truly unknown types\n\n3. **Architecture Patterns**\n   - **Base Classes** - Abstract common logic (e.g., `BaseMetadataProfileSync`, `BaseRootFolderSync`)\n   - **Type-Specific Implementations** - Extend base classes for each \\*arr type\n   - **Factory Pattern** - Use factories to instantiate correct implementation\n   - **Syncer Pattern** - Orchestrate sync operations (create/update/delete)\n\n4. **Code Organization**\n   - Group related functionality in directories (e.g., `metadataProfiles/`, `rootFolder/`)\n   - Use meaningful file names that reflect purpose\n   - Keep client abstractions in `clients/`\n   - Type definitions in `types/` or local `*.types.ts` files\n   - Generated API code in `__generated__/`\n\n## Project Structure\n\n```\nsrc/\n├── __generated__/         # Auto-generated API clients (don't modify)\n├── clients/               # API client abstractions\n│   ├── unified-client.ts  # Unified interface for all *arr types\n│   ├── radarr-client.ts\n│   ├── sonarr-client.ts\n│   └── ...\n├── metadataProfiles/      # Metadata profiles sync (Lidarr/Readarr)\n│   ├── metadataProfileBase.ts\n│   ├── metadataProfileLidarr.ts\n│   ├── metadataProfileReadarr.ts\n│   └── metadataProfileSyncer.ts\n├── rootFolder/            # Root folder sync\n├── types/                 # Type definitions\n│   ├── config.types.ts    # Configuration types\n│   ├── common.types.ts    # Shared types\n│   └── ...\n├── config.ts              # Configuration loading/merging\n├── index.ts               # Main entry point\n└── ...\n```\n\n## Key Concepts\n\n### \\*arr Type Support\n\nThe project supports multiple \\*arr applications with varying feature support:\n\n- **Full Support**: Sonarr v4, Radarr v5\n- **Experimental**: Lidarr, Readarr, Whisparr\n\n### Unified Client Pattern\n\nAll \\*arr clients implement `IArrClient` interface:\n\n- Provides consistent API across different \\*arr types\n- Optional methods for features not supported by all types (e.g., `getMetadataProfiles?()`)\n- Type-safe with generics for quality profiles, custom formats, etc.\n\n### Configuration System\n\n- **YAML-based** configuration with `config.yml`\n- **Template support** - Recyclarr templates, TRaSH Guides, local files, URLs\n- **Secrets management** - `!secret`, `!env` and `!file` tags for sensitive data\n- **Type-safe** - Zod schemas for validation\n\n### Sync Architecture\n\nEach feature (quality profiles, custom formats, metadata profiles, root folders) follows:\n\n1. **Load** - Fetch current server state\n2. **Calculate Diff** - Compare config vs. server\n3. **Sync** - Create/update/delete resources\n4. **Cleanup** - Optionally delete unmanaged items\n\n## Testing\n\n- **Unit tests**: `*.test.ts` files alongside source\n- **Samples**: Test data in `tests/samples/`\n- **Mocking**: Use Vitest mocks for API clients\n- **Coverage**: Run `pnpm coverage` to check coverage\n\n## Common Tasks\n\n### Adding Support for New \\*arr Feature\n\n1. Check if unified client needs new optional methods\n2. Create feature directory (e.g., `featureName/`)\n3. Implement base class with abstract methods\n4. Create type-specific implementations\n5. Add factory function and syncer\n6. Update main pipeline in `index.ts`\n7. Add tests\n8. Run: `pnpm build && pnpm test && pnpm lint && pnpm typecheck`\n\n### Modifying Existing Feature\n\n1. Locate relevant files (base class, implementations, syncer)\n2. Make changes following existing patterns\n3. Update tests\n4. Run: `pnpm build && pnpm test && pnpm lint && pnpm typecheck`\n\n### Adding New Configuration Options\n\n1. Update types in `types/config.types.ts`\n2. Update configuration merging in `config.ts`\n3. Implement feature logic\n4. Update documentation (if needed)\n5. Run all checks\n\n## Important Notes\n\n- **Never commit without passing all checks**: build, test, lint, typecheck\n- **Always use pnpm** - not npm or yarn\n- **Backward compatibility** - Maintain existing APIs when refactoring\n- **Type safety** - Prefer compile-time errors over runtime errors\n- **Logging** - Use the `logger` instance for consistent logging\n- **Error handling** - Graceful degradation, informative error messages\n\n## Resources\n\n- **Documentation**: https://configarr.de\n- **Repository**: https://github.com/raydak-labs/configarr\n- **TRaSH Guides**: https://trash-guides.info/\n- **Recyclarr Compatibility**: Config templates are compatible\n\n## Getting Help\n\nWhen implementing new features:\n\n1. Look for similar existing implementations\n2. Follow established patterns (especially rootFolder/metadataProfiles)\n3. Keep TypeScript strict typing\n4. Test thoroughly\n5. Ensure all checks pass\n"
  },
  {
    "path": ".claude/settings.json",
    "content": "{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(pnpm test:*)\",\n      \"Skill(superpowers:requesting-code-review)\",\n      \"Bash(git rev-parse:*)\",\n      \"Bash(pnpm run lint)\",\n      \"Bash(pnpm run typecheck)\",\n      \"Bash(pnpm typecheck)\",\n      \"Bash(pnpm lint:fix:*)\",\n      \"Bash(pnpm run build:*)\",\n      \"Bash(gh pr view:*)\",\n      \"Bash(git fetch:*)\",\n      \"Bash(git add:*)\",\n      \"Bash(git commit:*)\",\n      \"Bash(pnpm build:*)\",\n      \"Bash(pnpm lint:*)\"\n    ],\n    \"deny\": [],\n    \"ask\": []\n  }\n}\n"
  },
  {
    "path": ".dockerignore",
    "content": "node_modules\n.git\n.gitignore\n*.md\ndist\nrepos\ndockerrepos\nplaywright-report\ntest-results\nconfig.yml\nsecrets.yml\n"
  },
  {
    "path": ".env.template",
    "content": "#ROOT_PATH=/app\n#CUSTOM_REPO_ROOT=/app/repos\n#CONFIG_LOCATION=/app/config/config.yml\n#SECRETS_LOCATION=/app/config/secrets.yml\n#DRY_RUN=true # not fully supported yet\n#LOAD_LOCAL_SAMPLES=false\n#DEBUG_CREATE_FILES=false\n#LOG_LEVEL=info\n#TELEMETRY_ENABLED=false\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "ko_fi: blackdark93\ngithub: blackdark\n"
  },
  {
    "path": ".github/renovate.json5",
    "content": "{\n  $schema: \"https://docs.renovatebot.com/renovate-schema.json\",\n  extends: [\n    \"config:recommended\",\n    \"customManagers:githubActionsVersions\",\n    \"schedule:monthly\",\n    \":prConcurrentLimitNone\",\n    \":prHourlyLimitNone\",\n    \":pinVersions\",\n    \"security:minimumReleaseAgeNpm\",\n  ],\n  ignorePaths: [\n    \"**/node_modules/**\",\n    \"**/bower_components/**\",\n    \"**/vendor/**\",\n    \"**/__tests__/**\",\n    \"**/test/**\",\n    \"**/tests/**\",\n    \"**/__fixtures__/**\",\n  ],\n  labels: [\"dependencies\"],\n  packageRules: [\n    {\n      matchUpdateTypes: [\"minor\", \"patch\", \"pin\"],\n      automerge: true,\n      addLabels: [\"automerge\"],\n    },\n    {\n      groupName: \"devDependencies (non-major)\",\n      matchDepTypes: [\"devDependencies\"],\n      matchUpdateTypes: [\"patch\", \"minor\"],\n    },\n    {\n      matchPackageNames: [\"swagger-typescript-api\"],\n      groupName: null,\n    },\n    {\n      groupName: \"dependencies (non-major)\",\n      matchDepTypes: [\"dependencies\"],\n      matchUpdateTypes: [\"patch\", \"minor\"],\n    },\n    {\n      extends: [\"monorepo:docusaurus\"],\n      groupName: \"docusaurus monorepo\",\n      matchUpdateTypes: [\"digest\", \"patch\", \"minor\", \"major\"],\n    },\n    {\n      matchDatasources: [\"docker\"],\n      matchUpdateTypes: [\"minor\", \"patch\"],\n      groupName: \"all docker updates (without major)\",\n      groupSlug: \"all-docker\",\n    },\n  ],\n}\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Docker Builds\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - \"main\"\n    tags:\n      - \"v*.*.*\"\n    paths-ignore:\n      - \"docs/**\"\n      - \"**/*.md\"\n      - \".github/**\"\n      - \".vscode/**\"\n      - \"examples/**\"\n\npermissions:\n  contents: read\n  packages: write\n\n# A workflow run is made up of one or more jobs that can run sequentially or in parallel\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      # Get the repositery's code\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Inject enhanced GitHub environment variables\n        uses: rlespinasse/github-slug-action@v5\n\n      # https://github.com/docker/setup-qemu-action\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v4\n        with:\n          image: tonistiigi/binfmt:qemu-v8.1.5 # more recent\n\n      # https://github.com/docker/setup-buildx-action\n      - name: Set up Docker Buildx\n        id: buildx\n        uses: docker/setup-buildx-action@v4\n\n      # We push and build all types of images only when required\n      - name: Set build variables\n        id: vars\n        run: |\n          # Set version based on tag or branch\n          VERSION=\"${{ env.GITHUB_REF_SLUG }}\"\n\n          # Set build timestamp\n          BUILD_TIME=\"$(date -u +'%Y-%m-%dT%H:%M:%SZ')\"\n\n          echo \"version=$VERSION\" >> $GITHUB_OUTPUT\n          echo \"build_time=$BUILD_TIME\" >> $GITHUB_OUTPUT\n          echo \"run_id=${{ github.run_id }}\" >> $GITHUB_OUTPUT\n          echo \"repo=${{ github.repository }}\" >> $GITHUB_OUTPUT\n          echo \"sha=${{ github.sha }}\" >> $GITHUB_OUTPUT\n\n      - name: Set build platforms\n        id: set_platforms\n        run: |\n          if [[ ${{ github.event_name }} == 'workflow_dispatch' ]]; then\n            echo \"MANUAL_RUN=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"MANUAL_RUN=false\" >> $GITHUB_OUTPUT\n          fi\n\n          if [[ ${{ github.event_name }} == 'workflow_dispatch' || ${{ github.ref }} == refs/tags/* ]]; then\n            echo \"platforms=linux/amd64,linux/arm64\" >> $GITHUB_OUTPUT\n            echo \"push=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"platforms=linux/amd64\" >> $GITHUB_OUTPUT\n            echo \"push=false\" >> $GITHUB_OUTPUT\n          fi\n\n      #      - name: Available platforms\n      #        run: echo ${{ steps.buildx.outputs.platforms }}\n\n      - name: Login to GHCR\n        if: github.event_name != 'pull_request'\n        uses: docker/login-action@v4\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Login to Docker Hub\n        if: github.event_name != 'pull_request'\n        uses: docker/login-action@v4\n        with:\n          username: ${{ vars.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Docker meta for PMS\n        id: meta_pms\n        uses: docker/metadata-action@v6\n        with:\n          # list of Docker images to use as base name for tags\n          images: |\n            ghcr.io/raydak-labs/configarr\n            docker.io/configarr/configarr,enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'false' }}\n          # generate Docker tags based on the following events/attributes\n          tags: |\n            type=ref,event=branch,prefix=dev-,enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'true' }}\n            type=schedule,enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'false' }}\n            type=ref,event=branch,enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'false' }}\n            type=semver,pattern={{version}},enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'false' }}\n            type=semver,pattern={{major}}.{{minor}},enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'false' }}\n            type=semver,pattern={{major}},enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'false' }}\n\n      - name: Build and push PMS\n        uses: docker/build-push-action@v7\n        with:\n          context: .\n          file: ./Dockerfile\n          platforms: ${{ steps.set_platforms.outputs.platforms }}\n          push: ${{ steps.set_platforms.outputs.push }}\n          tags: ${{ steps.meta_pms.outputs.tags }}\n          labels: ${{ steps.meta_pms.outputs.labels }}\n          target: prod\n          build-args: |\n            CONFIGARR_VERSION=${{ steps.vars.outputs.version }}\n            BUILD_TIME=${{ steps.vars.outputs.build_time }}\n            GITHUB_RUN_ID=${{ steps.vars.outputs.run_id }}\n            GITHUB_REPO=${{ steps.vars.outputs.repo }}\n            GITHUB_SHA=${{ steps.vars.outputs.sha }}\n\n      - name: Docker meta for alpine3.22 image\n        id: meta_alpine322\n        uses: docker/metadata-action@v6\n        with:\n          images: |\n            ghcr.io/raydak-labs/configarr\n            docker.io/configarr/configarr,enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'false' }}\n          tags: |\n            type=ref,event=branch,prefix=dev-,suffix=-alpine3.22,enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'true' }}\n            type=schedule,suffix=-alpine3.22,enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'false' }}\n            type=ref,event=branch,suffix=-alpine3.22,enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'false' }}\n            type=semver,pattern={{version}},suffix=-alpine3.22,enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'false' }}\n            type=semver,pattern={{major}}.{{minor}},suffix=-alpine3.22,enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'false' }}\n            type=semver,pattern={{major}},suffix=-alpine3.22,enable=${{ steps.set_platforms.outputs.MANUAL_RUN == 'false' }}\n\n      - name: Build and push alpine3.22 image\n        uses: docker/build-push-action@v7\n        with:\n          context: .\n          file: ./Dockerfile\n          platforms: ${{ steps.set_platforms.outputs.platforms }}\n          push: ${{ steps.set_platforms.outputs.push }}\n          tags: ${{ steps.meta_alpine322.outputs.tags }}\n          labels: ${{ steps.meta_alpine322.outputs.labels }}\n          target: prod-alpine-3-22\n          build-args: |\n            CONFIGARR_VERSION=${{ steps.vars.outputs.version }}\n            BUILD_TIME=${{ steps.vars.outputs.build_time }}\n            GITHUB_RUN_ID=${{ steps.vars.outputs.run_id }}\n            GITHUB_REPO=${{ steps.vars.outputs.repo }}\n            GITHUB_SHA=${{ steps.vars.outputs.sha }}\n\n      # - name: Docker Hub Description\n      #   if: github.event_name != 'pull_request'\n      #   uses: peter-evans/dockerhub-description@v3\n      #   with:\n      #     username: ${{ secrets.DOCKERHUB_USERNAME }}\n      #     password: ${{ secrets.DOCKERHUB_PASSWORD }}\n      #     repository: pabloromeo/clusterplex_pms\n      #     readme-filepath: ./README.md\n      #     short-description: \"PMS image for ClusterPlex\"\n"
  },
  {
    "path": ".github/workflows/cli-release.yml",
    "content": "name: CLI Release\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n  workflow_dispatch:\n    inputs:\n      baseline:\n        description: \"Enable baseline builds for older CPU architectures\"\n        required: false\n        type: boolean\n        default: false\n\npermissions:\n  contents: write\n\njobs:\n  build-cli:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        include:\n          - target: bun-linux-x64\n            platform: linux-x64\n          - target: bun-linux-arm64\n            platform: linux-arm64\n          - target: bun-darwin-x64\n            platform: darwin-x64\n          - target: bun-darwin-arm64\n            platform: darwin-arm64\n          - target: bun-windows-x64\n            platform: windows-x64\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Inject enhanced GitHub environment variables\n        uses: rlespinasse/github-slug-action@v5\n\n      - uses: pnpm/action-setup@v5\n        with:\n          version: latest\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version: lts/*\n          cache: pnpm\n\n      - name: Setup Bun\n        uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n\n      - name: Install deps\n        run: |\n          pnpm i\n\n      - name: Set build variables\n        id: vars\n        run: |\n          # Set version based on tag or branch\n          VERSION=\"${{ env.GITHUB_REF_SLUG }}\"\n\n          # Set build timestamp\n          BUILD_TIME=\"$(date -u +'%Y-%m-%dT%H:%M:%SZ')\"\n\n          echo \"version=$VERSION\" >> $GITHUB_OUTPUT\n          echo \"build_time=$BUILD_TIME\" >> $GITHUB_OUTPUT\n          echo \"run_id=${{ github.run_id }}\" >> $GITHUB_OUTPUT\n          echo \"repo=${{ github.repository }}\" >> $GITHUB_OUTPUT\n          echo \"sha=${{ github.sha }}\" >> $GITHUB_OUTPUT\n\n      - name: Build CLI\n        run: |\n          FLAGS=\"\"\n\n          # Set Windows-specific flags if building for Windows\n          if [[ \"${{ matrix.platform }}\" == windows-* ]]; then\n            # those flags are currently only supported when build in windows\n            #FLAGS=\"${FLAGS} --windows-title \\\"Configarr\\\" --windows-publisher \\\"BlackDark\\\" --windows-version \\\"${{ steps.vars.outputs.version }}\\\" --windows-description \\\"A powerful configuration management tool for *Arr applications\\\" --windows-copyright \\\"© 2025 BlackDark\\\"\"\n            echo \"No custom flags for windows\"\n          else\n            FLAGS=\"${FLAGS} --bytecode\"\n          fi\n\n          # Set baseline target if requested\n          if [[ \"${{ github.event.inputs.baseline }}\" == \"true\" ]]; then\n            FLAGS=\"${FLAGS} --target=bun\"\n          else\n            echo \"Non baseline build\"\n          fi\n\n          echo \"Using additional build flags: $FLAGS\"\n\n          bun build src/index.ts \\\n            --compile --minify --sourcemap src/index.ts \\\n            --outfile configarr \\\n            --define 'process.env.CONFIGARR_VERSION=\"${{ steps.vars.outputs.version }}\"' \\\n            --define 'process.env.BUILD_TIME=\"${{ steps.vars.outputs.build_time }}\"' \\\n            --define 'process.env.GITHUB_RUN_ID=\"${{ steps.vars.outputs.run_id }}\"' \\\n            --define 'process.env.GITHUB_REPO=\"${{ steps.vars.outputs.repo }}\"' \\\n            --define 'process.env.GITHUB_SHA=\"${{ steps.vars.outputs.sha }}\"' \\\n            --define 'process.env.BUILD_PLATFORM=\"${{ matrix.platform }}\"' \\\n            --target=${{ matrix.target }} \\\n            $FLAGS\n\n      - name: Compress CLI\n        run: tar -cJf configarr-${{ matrix.platform }}.tar.xz configarr*\n\n      - name: Upload CLI artifact\n        uses: actions/upload-artifact@v7\n        with:\n          name: configarr-${{ matrix.platform }}.tar.xz\n          path: configarr-${{ matrix.platform }}.tar.xz\n          retention-days: 1\n\n  release:\n    needs: build-cli\n    if: startsWith(github.ref, 'refs/tags/')\n    runs-on: ubuntu-latest\n    steps:\n      - name: Download CLI artifacts\n        uses: actions/download-artifact@v8\n        with:\n          path: artifacts\n\n      - name: Extract tar.gz files\n        run: |\n          find artifacts -name \"*.tar.gz\" -type f -exec sh -c '\n            dir=$(dirname \"$1\")\n            base=$(basename \"$1\" .tar.gz)\n            mkdir -p \"$dir/extracted\"\n            tar -xzf \"$1\" -C \"$dir/extracted\"\n            mv \"$dir/extracted\"/* \"$dir/\" 2>/dev/null || true\n            rmdir \"$dir/extracted\" 2>/dev/null || true\n          ' _ {} \\;\n\n      - name: Create Release\n        uses: softprops/action-gh-release@v2\n        with:\n          tag_name: ${{ github.ref_name }}\n          files: artifacts/**/configarr-*.tar.xz\n          generate_release_notes: false\n"
  },
  {
    "path": ".github/workflows/doc-preview.yml",
    "content": "name: Documentation (Preview)\n\non:\n  pull_request:\n    branches: [main]\n    paths:\n      - docs/**\n  workflow_dispatch:\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    name: Deploy\n    permissions:\n      contents: read\n      deployments: write\n      pull-requests: write\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Inject enhanced GitHub environment variables\n        uses: rlespinasse/github-slug-action@v5\n\n      - uses: pnpm/action-setup@v5\n        with:\n          version: latest\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version: lts/*\n          cache: pnpm\n\n      - name: Install & build\n        working-directory: docs\n        run: |\n          pnpm i\n          pnpm build\n\n      - name: Deploy\n        uses: cloudflare/wrangler-action@v3\n        id: cloudflare-deploy\n        with:\n          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}\n          command: pages deploy ./docs/build --project-name=configarr-preview --branch=${{ env.GITHUB_REF_SLUG_URL }}\n          # Optional: Enable this if you want to have GitHub Deployments triggered\n          #gitHubToken: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Comment PR\n        uses: thollander/actions-comment-pull-request@v3\n        if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main' }}\n        with:\n          message: |\n            Doc preview deployed to: ${{ steps.cloudflare-deploy.outputs.deployment-url }}\n          comment-tag: docs-preview\n"
  },
  {
    "path": ".github/workflows/documentation.yml",
    "content": "name: Documentation\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n    paths:\n      - docs/**\n  workflow_dispatch:\n\njobs:\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - uses: pnpm/action-setup@v5\n        with:\n          version: latest\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version: lts/*\n          cache: pnpm\n\n      - name: Install & build\n        working-directory: docs\n        run: |\n          pnpm i\n          pnpm build\n\n      - name: Upload static files as artifact\n        id: deployment\n        uses: actions/upload-pages-artifact@v4 # or specific \"vX.X.X\" version tag for this action\n        with:\n          path: ./docs/build\n\n  # Deploy job\n  deploy:\n    if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'\n\n    # Add a dependency to the build job\n    needs: build\n\n    # Grant GITHUB_TOKEN the permissions required to make a Pages deployment\n    permissions:\n      pages: write # to deploy to Pages\n      id-token: write # to verify the deployment originates from an appropriate source\n\n    # Deploy to the github-pages environment\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n\n    # Specify runner + deployment step\n    runs-on: ubuntu-latest\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v5 # or specific \"vX.X.X\" version tag for this action\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches-ignore:\n      - prod\n\njobs:\n  test:\n    name: Lint & Test\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - uses: pnpm/action-setup@v5\n        with:\n          version: latest\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version: lts/*\n          cache: pnpm\n\n      - run: pnpm install\n\n      - name: Lint\n        run: pnpm lint\n\n      - name: Typecheck\n        run: pnpm typecheck\n\n      - name: Test\n        run: pnpm test\n"
  },
  {
    "path": ".github/workflows/release-it.yml",
    "content": "name: release-it\n\non:\n  workflow_dispatch:\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Generate a token\n        id: generate_token\n        uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0\n        with:\n          app_id: ${{ secrets.RELEASER_APP_ID }}\n          private_key: ${{ secrets.RELEASER_PRIVATE_KEY }}\n\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          token: ${{ steps.generate_token.outputs.token }}\n          fetch-depth: 0\n\n      - name: git config\n        run: |\n          git config user.email \"raydak-releaser@users.noreply.github.com\"\n          git config user.name \"raydak-releaser[bot]\"\n\n      - uses: pnpm/action-setup@v5\n        with:\n          version: latest\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version: lts/*\n          cache: pnpm\n\n      - run: pnpm install\n\n      - run: pnpm release-it --ci\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by https://www.toptal.com/developers/gitignore/api/node\n# Edit at https://www.toptal.com/developers/gitignore?templates=node\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n### Node Patch ###\n# Serverless Webpack directories\n.webpack/\n\n# Optional stylelint cache\n\n# SvelteKit build / generate output\n.svelte-kit\n\n# End of https://www.toptal.com/developers/gitignore/api/node\n\n\n\n*.env\n!.env.template\n/test-results/\n/playwright-report/\n/blob-report/\n/playwright/.cache/\n\nconfig.yml\nsecrets.yml\n\ntest*.json\nrepos/\ndockerrepos\n.DS_Store\n\nbundle.cjs\n\n*.bun-build\ncli-bun\ncli-deno\n\nresult\n\n# Git worktrees\n.worktrees/\n.claude/settings.local.json\n"
  },
  {
    "path": ".prettierignore",
    "content": "pnpm-lock.yaml\ndocs/build\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "/** @type {import(\"prettier\").Options} */\nexport default {\n  printWidth: 140,\n};\n"
  },
  {
    "path": ".release-it.json",
    "content": "{\n  \"npm\": false,\n  \"git\": {\n    \"commitMessage\": \"chore: release ${version}\",\n    \"requireCleanWorkingDir\": false\n  },\n  \"github\": {\n    \"release\": true,\n    \"comments\": {\n      \"submit\": false\n    }\n  },\n  \"plugins\": {\n    \"@release-it/conventional-changelog\": {\n      \"preset\": {\n        \"name\": \"conventionalcommits\",\n        \"types\": [\n          {\n            \"type\": \"feat\",\n            \"section\": \"Features\"\n          },\n          {\n            \"type\": \"fix\",\n            \"section\": \"Bug Fixes\"\n          },\n          {\n            \"type\": \"refactor\",\n            \"section\": \"(internal) Refactorings\"\n          }\n        ]\n      },\n      \"infile\": \"CHANGELOG.md\",\n      \"header\": \"# Changelog\\n\\nAll notable changes to this project will be documented in this file.\\n\"\n    }\n  },\n  \"hooks\": {\n    \"before:git:release\": [\"pnpm exec prettier . --write\", \"git add .\", \"pnpm run lint\"],\n    \"after:release\": \"echo \\\"RELEASED_VERSION=${version}\\\" >> $GITHUB_ENV\"\n  }\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  \"version\": \"0.2.0\",\n\n  \"configurations\": [\n    {\n      \"command\": \"pnpm start\",\n      \"name\": \"pnpm start\",\n      \"request\": \"launch\",\n      \"type\": \"node-terminal\"\n    },\n    {\n      \"name\": \"tsx\",\n      \"type\": \"node\",\n      \"request\": \"launch\",\n\n      // Debug current file in VSCode\n      \"program\": \"${file}\",\n\n      /*\n      Path to tsx binary\n      Assuming locally installed\n      */\n      \"runtimeExecutable\": \"${workspaceFolder}/node_modules/.bin/tsx\",\n\n      /*\n      Open terminal when debugging starts (Optional)\n      Useful to see console.logs\n      */\n      \"console\": \"integratedTerminal\",\n      \"internalConsoleOptions\": \"neverOpen\",\n\n      // Files to exclude from debugger (e.g. call stack)\n      \"skipFiles\": [\n        // Node.js internal core modules\n        \"<node_internals>/**\",\n\n        // Ignore all dependencies (optional)\n        \"${workspaceFolder}/node_modules/**\"\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"yaml.customTags\": [\"!secret\", \"!env\", \"!file\"]\n}\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# Configarr - AI Agent Quick Start Guide\n\n## Project Overview\n\n**Configarr** is a configuration and synchronization tool for \\*arr applications (Sonarr, Radarr, Lidarr, Readarr, Whisparr). It integrates with TRaSH Guides to automate custom formats, quality profiles, and other settings.\n\n- **Language**: TypeScript (Node.js)\n- **Package Manager**: **pnpm** (always use `pnpm`, never `npm` or `yarn`)\n- **Build Tool**: esbuild\n- **Test Framework**: Vitest\n- **Code Style**: Prettier\n\n## Quick Setup\n\n```bash\n# Install dependencies\npnpm install\n\n# Development\npnpm start              # Run the application\npnpm test              # Run tests\npnpm test:watch        # Run tests in watch mode\npnpm build             # Build for production\npnpm lint              # Check formatting\npnpm lint:fix          # Fix formatting\npnpm typecheck         # TypeScript type checking\n```\n\n## Development Rules\n\n### ✅ Must Do After Every Implementation\n\n1. **Run all three checks** - ALL must pass before considering work complete:\n   ```bash\n   pnpm build && pnpm test && pnpm lint\n   ```\n2. **Type checking** - Ensure no TypeScript errors:\n   ```bash\n   pnpm typecheck\n   ```\n\n### 🎯 Coding Standards\n\n1. **Follow Existing Patterns**\n   - Study similar existing code before implementing new features\n   - Maintain consistency with current architecture\n   - Use established patterns (e.g., rootFolder pattern for new modules)\n\n2. **TypeScript Best Practices**\n   - Use strict typing - avoid `any` when possible\n   - Prefer interfaces for public APIs, types for internal use\n   - Use type inference where it improves readability\n   - Leverage union types and discriminated unions\n   - Use `unknown` instead of `any` for truly unknown types\n\n3. **Architecture Patterns**\n   - **Base Classes** - Abstract common logic (e.g., `BaseMetadataProfileSync`, `BaseRootFolderSync`)\n   - **Type-Specific Implementations** - Extend base classes for each \\*arr type\n   - **Factory Pattern** - Use factories to instantiate correct implementation\n   - **Syncer Pattern** - Orchestrate sync operations (create/update/delete)\n\n4. **Code Organization**\n   - Group related functionality in directories (e.g., `metadataProfiles/`, `rootFolder/`)\n   - Use meaningful file names that reflect purpose\n   - Keep client abstractions in `clients/`\n   - Type definitions in `types/` or local `*.types.ts` files\n   - Generated API code in `__generated__/`\n\n## Project Structure\n\n```\nsrc/\n├── __generated__/         # Auto-generated API clients (don't modify)\n├── clients/               # API client abstractions\n│   ├── unified-client.ts  # Unified interface for all *arr types\n│   ├── radarr-client.ts\n│   ├── sonarr-client.ts\n│   └── ...\n├── metadataProfiles/      # Metadata profiles sync (Lidarr/Readarr)\n│   ├── metadataProfileBase.ts\n│   ├── metadataProfileLidarr.ts\n│   ├── metadataProfileReadarr.ts\n│   └── metadataProfileSyncer.ts\n├── rootFolder/            # Root folder sync\n├── types/                 # Type definitions\n│   ├── config.types.ts    # Configuration types\n│   ├── common.types.ts    # Shared types\n│   └── ...\n├── config.ts              # Configuration loading/merging\n├── index.ts               # Main entry point\n└── ...\n```\n\n## Key Concepts\n\n### \\*arr Type Support\n\nThe project supports multiple \\*arr applications with varying feature support:\n\n- **Full Support**: Sonarr v4, Radarr v5\n- **Experimental**: Lidarr, Readarr, Whisparr\n\n### Unified Client Pattern\n\nAll \\*arr clients implement `IArrClient` interface:\n\n- Provides consistent API across different \\*arr types\n- Optional methods for features not supported by all types (e.g., `getMetadataProfiles?()`)\n- Type-safe with generics for quality profiles, custom formats, etc.\n\n### Configuration System\n\n- **YAML-based** configuration with `config.yml`\n- **Template support** - Recyclarr templates, TRaSH Guides, local files, URLs\n- **Secrets management** - `!secret`, `!env` and `!file` tags for sensitive data\n- **Type-safe** - Zod schemas for validation\n\n### Sync Architecture\n\nEach feature (quality profiles, custom formats, metadata profiles, root folders) follows:\n\n1. **Load** - Fetch current server state\n2. **Calculate Diff** - Compare config vs. server\n3. **Sync** - Create/update/delete resources\n4. **Cleanup** - Optionally delete unmanaged items\n\n## Testing\n\n- **Unit tests**: `*.test.ts` files alongside source\n- **Samples**: Test data in `tests/samples/`\n- **Mocking**: Use Vitest mocks for API clients\n- **Coverage**: Run `pnpm coverage` to check coverage\n\n## Common Tasks\n\n### Adding Support for New \\*arr Feature\n\n1. Check if unified client needs new optional methods\n2. Create feature directory (e.g., `featureName/`)\n3. Implement base class with abstract methods\n4. Create type-specific implementations\n5. Add factory function and syncer\n6. Update main pipeline in `index.ts`\n7. Add tests\n8. Run: `pnpm build && pnpm test && pnpm lint && pnpm typecheck`\n\n### Modifying Existing Feature\n\n1. Locate relevant files (base class, implementations, syncer)\n2. Make changes following existing patterns\n3. Update tests\n4. Run: `pnpm build && pnpm test && pnpm lint && pnpm typecheck`\n\n### Adding New Configuration Options\n\n1. Update types in `types/config.types.ts`\n2. Update configuration merging in `config.ts`\n3. Implement feature logic\n4. Update documentation (if needed)\n5. Run all checks\n\n## Important Notes\n\n- **Never commit without passing all checks**: build, test, lint, typecheck\n- **Always use pnpm** - not npm or yarn\n- **Backward compatibility** - Maintain existing APIs when refactoring\n- **Type safety** - Prefer compile-time errors over runtime errors\n- **Logging** - Use the `logger` instance for consistent logging\n- **Error handling** - Graceful degradation, informative error messages\n\n## Resources\n\n- **Documentation**: https://configarr.de\n- **Repository**: https://github.com/raydak-labs/configarr\n- **TRaSH Guides**: https://trash-guides.info/\n- **Recyclarr Compatibility**: Config templates are compatible\n\n## Getting Help\n\nWhen implementing new features:\n\n1. Look for similar existing implementations\n2. Follow established patterns (especially rootFolder/metadataProfiles)\n3. Keep TypeScript strict typing\n4. Test thoroughly\n5. Ensure all checks pass\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n## [1.27.0](https://github.com/raydak-labs/configarr/compare/v1.26.0...v1.27.0) (2026-04-14)\n\n### Features\n\n- add silenceTrashConflictWarnings config option ([b42072b](https://github.com/raydak-labs/configarr/commit/b42072bcff7d4532ba0ab42b749fbf1321fac0d9))\n\n## [1.26.0](https://github.com/raydak-labs/configarr/compare/v1.25.0...v1.26.0) (2026-04-13)\n\n### Features\n\n- warn on TRaSH CF conflicts ([e5e7387](https://github.com/raydak-labs/configarr/commit/e5e738759a173e68152b53583ecbd846acf9aaa2))\n\n### Bug Fixes\n\n- address review findings in CF conflict handling ([92e807a](https://github.com/raydak-labs/configarr/commit/92e807a335cee08e675e0fed435ec66fa17f7a22))\n- adjust conflicting cf names with different ids ([65c369d](https://github.com/raydak-labs/configarr/commit/65c369d9877e67534325a05ee7933d8309b44733))\n- **deps:** update dependencies (non-major) ([#418](https://github.com/raydak-labs/configarr/issues/418)) ([50fff41](https://github.com/raydak-labs/configarr/commit/50fff41915bee98cd73b68aae025cca55dae9947))\n- improve trash conflicts.json handling ([763ebef](https://github.com/raydak-labs/configarr/commit/763ebef84438f0fc13b76e6b8a6e0b9877704eff))\n\n### (internal) Refactorings\n\n- **internal:** move ky-client + mergedTypes from **generated** ([55acfd1](https://github.com/raydak-labs/configarr/commit/55acfd1ed7f2d0e19c6d158be5c122da69ce10d2))\n- try improving error messages ([e5fcf78](https://github.com/raydak-labs/configarr/commit/e5fcf785b7094c5844a13d63141247a11502eb7a))\n\n## [1.25.0](https://github.com/raydak-labs/configarr/compare/v1.24.1...v1.25.0) (2026-03-30)\n\n### Features\n\n- add support to include trash guide quality definitions by id ([#406](https://github.com/raydak-labs/configarr/issues/406)) ([c196221](https://github.com/raydak-labs/configarr/commit/c196221956e83b885d7228ad117986817c78ffe9))\n\n### Bug Fixes\n\n- prevent duplicate CF creates and fix error handling in manageCf ([7eff22e](https://github.com/raydak-labs/configarr/commit/7eff22efb9b20acbbd026fb47b3e13ea0ba0d3e0))\n\n## [1.24.1](https://github.com/raydak-labs/configarr/compare/v1.24.0...v1.24.1) (2026-03-29)\n\n### Bug Fixes\n\n- introduce alpine3.22 images ([#411](https://github.com/raydak-labs/configarr/issues/411)) ([7f6a899](https://github.com/raydak-labs/configarr/commit/7f6a899ffab8f962d34e58d98c7d3f8f68342ff5))\n\n## [1.24.0](https://github.com/raydak-labs/configarr/compare/v1.23.0...v1.24.0) (2026-03-09)\n\n### Features\n\n- add use_default_score flag to override CF scores ([#397](https://github.com/raydak-labs/configarr/issues/397)) ([3d7deeb](https://github.com/raydak-labs/configarr/commit/3d7deebd0fd5b57be34bbcec1955c02e3953fa1d))\n\n### Bug Fixes\n\n- correctly handle new profiles with not allowed upgrades ([5a3d8c1](https://github.com/raydak-labs/configarr/commit/5a3d8c1aa0e6b84808d32ff351cc16fca393664a)), closes [#408](https://github.com/raydak-labs/configarr/issues/408)\n- **deps:** update dependencies (non-major) ([c3eb357](https://github.com/raydak-labs/configarr/commit/c3eb35730cc02bf1d1f3945789a93b6b84e6890a))\n\n### (internal) Refactorings\n\n- adjust example runs ([8e3ec43](https://github.com/raydak-labs/configarr/commit/8e3ec434e9ee3b451b7757dd42731c3ad1b8e490))\n\n## [1.23.0](https://github.com/raydak-labs/configarr/compare/v1.22.0...v1.23.0) (2026-02-23)\n\n### Features\n\n- add enable merge option ([#394](https://github.com/raydak-labs/configarr/issues/394)) ([97d165d](https://github.com/raydak-labs/configarr/commit/97d165d3dc8af475cc51c4e89d9f5ca8ede831c8))\n\n## [1.22.0](https://github.com/raydak-labs/configarr/compare/v1.21.0...v1.22.0) (2026-02-20)\n\n### Features\n\n- support the breaking changes from TRaSH-Guide. IMPORTANT ([#393](https://github.com/raydak-labs/configarr/issues/393)) ([b2320ea](https://github.com/raydak-labs/configarr/commit/b2320ead2194f9bd0e8268075b582d509bbbb96b))\n\n## [1.21.0](https://github.com/raydak-labs/configarr/compare/v1.20.0...v1.21.0) (2026-02-17)\n\n### Features\n\n- add support for multiple + glob secrets ([69259b1](https://github.com/raydak-labs/configarr/commit/69259b15aa9230c000addea910faab11ad26a30a))\n- **config:** Add support for managing UI Config settings ([fd7af46](https://github.com/raydak-labs/configarr/commit/fd7af463058768f89621a8b72e7be15b59ce9628))\n\n### Bug Fixes\n\n- cloning problems when switching urls ([#389](https://github.com/raydak-labs/configarr/issues/389)) ([1cc2c69](https://github.com/raydak-labs/configarr/commit/1cc2c6909116ca8b93b6a842d7abda8ca1792b0c)), closes [#388](https://github.com/raydak-labs/configarr/issues/388)\n- **deps:** update dependencies (non-major) ([546084b](https://github.com/raydak-labs/configarr/commit/546084bb4238efe621d1364405b311ae57359ce3))\n- ignore api key when syncing download clients ([356b141](https://github.com/raydak-labs/configarr/commit/356b14101686f43ba9d66504e7db8c2e6077505c))\n\n## [1.20.0](https://github.com/raydak-labs/configarr/compare/v1.19.1...v1.20.0) (2026-01-10)\n\n### Features\n\n- add Readarr root folder sync support ([8d757de](https://github.com/raydak-labs/configarr/commit/8d757de5d7e4247aebf366f5dc7773d12eda90a3)), closes [raydak-labs/configarr#264](https://github.com/raydak-labs/configarr/issues/264)\n- add remote path mapping for download clients ([dd7ab2a](https://github.com/raydak-labs/configarr/commit/dd7ab2a3e6138953a850099fd2867059b4358cee))\n\n### (internal) Refactorings\n\n- add generics for better types ([914c2c6](https://github.com/raydak-labs/configarr/commit/914c2c6f14caf9c2ac89b869dcae1226bf90c41c))\n- better use client specific things ([042c8e5](https://github.com/raydak-labs/configarr/commit/042c8e528a1909e246c81299b5293ec11da2401c))\n- review changes ([0b12b53](https://github.com/raydak-labs/configarr/commit/0b12b5331717b0d8b2a55a628890db7255d36577))\n\n## [1.19.1](https://github.com/raydak-labs/configarr/compare/v1.19.0...v1.19.1) (2026-01-10)\n\n### Bug Fixes\n\n- repair broken changelog links and update release-it plugin ([570b90e](https://github.com/raydak-labs/configarr/commit/570b90e12b3a10e1295446f59456b5fd27d79750)), closes [#355](https://github.com/raydak-labs/configarr/issues/355)\n- try implementing a fix for legacy kernels in the git checkout process ([778d6ba](https://github.com/raydak-labs/configarr/commit/778d6ba7fe0050ea981bda24a5b634cc1838a756))\n\n## [1.19.0](https://github.com/raydak-labs/configarr/compare/v1.18.0...v1.19.0) (2025-12-19)\n\n### Features\n\n- add download client sync dba819f\n- Add file tag 9582747\n- add support for metadata profiles (lidarr, readarr) 3b7595c\n- download client general configuration 53fccee\n\n### Bug Fixes\n\n- changing remote repos should work without manual intervention f39bac2, closes #359\n- correctly handle metadata sync for unrelated services 2cfee60\n- **deps:** update dependencies (non-major) ([#355](https://github.com/raydak-labs/configarr/issues/355)) fd70b7b\n\n### (internal) Refactorings\n\n- improve error logs a352649\n\n## [1.18.0](https://github.com/raydak-labs/configarr/compare/v1.17.2...v1.18.0) (2025-11-23)\n\n### Features\n\n- add nixos module ([#341](https://github.com/raydak-labs/configarr/issues/341)) ([eabd7a2](https://github.com/raydak-labs/configarr/commit/eabd7a2c2de0151c86edaa76df41de9dce368e19))\n- add support for deleting unmanaged quality profiles ([#340](https://github.com/raydak-labs/configarr/issues/340)) ([60bc77d](https://github.com/raydak-labs/configarr/commit/60bc77d00c0c79fbe82f6351ef4a2c01562e631c))\n- add support for loading remote templates ([#342](https://github.com/raydak-labs/configarr/issues/342)) ([46ddfc8](https://github.com/raydak-labs/configarr/commit/46ddfc86b9b29c0cdc01ddebc2655aad40fb4ecf)), closes [#301](https://github.com/raydak-labs/configarr/issues/301)\n\n## [1.17.2](https://github.com/raydak-labs/configarr/compare/v1.17.1...v1.17.2) (2025-11-08)\n\n### Bug Fixes\n\n- **deps:** update dependencies (non-major) ([#332](https://github.com/raydak-labs/configarr/issues/332)) ([7cbf309](https://github.com/raydak-labs/configarr/commit/7cbf309d951cc3fbb18e0f8b8c0548d1f8a9cd46))\n- **deps:** update dependency pino to v10 ([2c2c84c](https://github.com/raydak-labs/configarr/commit/2c2c84c09bba48ef58e2e531d4a72d7f9a59915e))\n- **deps:** update dependency zod to v4 ([73be722](https://github.com/raydak-labs/configarr/commit/73be722186fd42c014561c99de312b486f94e229))\n- **deps:** update docusaurus monorepo to v3.9.2 ([#329](https://github.com/raydak-labs/configarr/issues/329)) ([d7a1276](https://github.com/raydak-labs/configarr/commit/d7a1276e87f3d0a2c6ccd5623b7cc422545cd36c))\n- root folder for lidarr sync ([6c67999](https://github.com/raydak-labs/configarr/commit/6c67999b54cdd32c4bd119f5818cf227fcaa7eb3))\n- windows binary builds ([dfc2982](https://github.com/raydak-labs/configarr/commit/dfc2982af10111d6e167bfeb3c6bb680148854d4))\n\n### (internal) Refactorings\n\n- restructure rootFolder code ([2f5b83d](https://github.com/raydak-labs/configarr/commit/2f5b83dd11e14ff85868b0c964d368212990a82a))\n\n## [1.17.1](https://github.com/raydak-labs/configarr/compare/v1.17.0...v1.17.1) (2025-10-10)\n\n### Bug Fixes\n\n- binary variables ([a35408c](https://github.com/raydak-labs/configarr/commit/a35408c9b96c9cd6c7b863ef2c156a8aacbc9dc1))\n\n## [1.17.0](https://github.com/raydak-labs/configarr/compare/v1.16.0...v1.17.0) (2025-10-09)\n\n### Features\n\n- add configarr binary ([bd8819b](https://github.com/raydak-labs/configarr/commit/bd8819b54d41778a5b52323edfc476719657d009)), closes [#272](https://github.com/raydak-labs/configarr/issues/272)\n\n### Bug Fixes\n\n- **deps:** update dependencies (non-major) ([#314](https://github.com/raydak-labs/configarr/issues/314)) ([5afcd36](https://github.com/raydak-labs/configarr/commit/5afcd36c3ce5091fc10d078bb05a982008f580ab))\n- **deps:** update docusaurus monorepo to v3.9.1 ([1b0a4b2](https://github.com/raydak-labs/configarr/commit/1b0a4b2fa2c7e9b5651af2e0931ba9fec360ed2b))\n\n## [1.16.0](https://github.com/raydak-labs/configarr/compare/v1.15.1...v1.16.0) (2025-09-27)\n\n### Features\n\n- add possibity to add custom formats without assign to profile ([88b78ca](https://github.com/raydak-labs/configarr/commit/88b78ca1c1dddeb604a6348d4589491475b72b12)), closes [#279](https://github.com/raydak-labs/configarr/issues/279)\n- add telemetry - default disabled ([a0fc6d0](https://github.com/raydak-labs/configarr/commit/a0fc6d08b97b147ab168e77ade8f9a4375c0f12c))\n- implement score for custom format groups closes [#196](https://github.com/raydak-labs/configarr/issues/196) ([2933d0f](https://github.com/raydak-labs/configarr/commit/2933d0f011ff12eb8b8f15952e8df7824110913f))\n\n## [1.15.1](https://github.com/raydak-labs/configarr/compare/v1.15.0...v1.15.1) (2025-09-04)\n\n### Bug Fixes\n\n- recursively load all recyclarr templates and not only first level of directory ([374b767](https://github.com/raydak-labs/configarr/commit/374b767a470530b91561725e272a22fd15b3ff15)), closes [#305](https://github.com/raydak-labs/configarr/issues/305)\n\n## [1.15.0](https://github.com/raydak-labs/configarr/compare/v1.14.1...v1.15.0) (2025-08-27)\n\n### Features\n\n- include all relevant CF-Groups (and marked CFs) for TRaSH-Guide profiles automatically ([065de47](https://github.com/raydak-labs/configarr/commit/065de471e4d2feecd198dcc33753933e216b9284)), closes [/github.com/TRaSH-Guides/Guides/pull/2455#discussion_r2297832409](https://github.com/raydak-labs//github.com/TRaSH-Guides/Guides/pull/2455/issues/discussion_r2297832409)\n\n## [1.14.1](https://github.com/raydak-labs/configarr/compare/v1.14.0...v1.14.1) (2025-08-13)\n\n### Bug Fixes\n\n- correctly handle group cfs in mix with cfs ([8974eac](https://github.com/raydak-labs/configarr/commit/8974eac26b213c5ea3a1a437765b92fb2831bedc)), closes [#289](https://github.com/raydak-labs/configarr/issues/289)\n\n## [1.14.0](https://github.com/raydak-labs/configarr/compare/v1.13.7...v1.14.0) (2025-08-04)\n\n### Features\n\n- add support for root folders ([9e717ff](https://github.com/raydak-labs/configarr/commit/9e717ff25184bec830dbec5fad0faab346f73c21)), closes [#264](https://github.com/raydak-labs/configarr/issues/264)\n- implement delay profiles ([19c7bbe](https://github.com/raydak-labs/configarr/commit/19c7bbe5d65148393ddeaad37d33d21fd5010c0f))\n\n### Bug Fixes\n\n- **deps:** update dependencies (non-major) ([#292](https://github.com/raydak-labs/configarr/issues/292)) ([3c46cd7](https://github.com/raydak-labs/configarr/commit/3c46cd7166cafb85ef3019400f43851feadc52a6))\n\n### (internal) Refactorings\n\n- update generated api ([b188e8b](https://github.com/raydak-labs/configarr/commit/b188e8b1e307001795eab6610944e9ffd8f021e0))\n\n## [1.13.7](https://github.com/raydak-labs/configarr/compare/v1.13.6...v1.13.7) (2025-07-22)\n\n### Bug Fixes\n\n- do not delete CFs when dry run enabled fixes [#290](https://github.com/raydak-labs/configarr/issues/290) ([cff234b](https://github.com/raydak-labs/configarr/commit/cff234be81fe5dc576ee0be1f21eb45442a5cf45))\n\n## [1.13.6](https://github.com/raydak-labs/configarr/compare/v1.13.5...v1.13.6) (2025-07-09)\n\n### Bug Fixes\n\n- correctly lookup quality definition names from quality profiles fix [#281](https://github.com/raydak-labs/configarr/issues/281) ([728cbde](https://github.com/raydak-labs/configarr/commit/728cbde67f2da82805934286f0aa69ba9bbfd85b))\n- **deps:** update dependencies (non-major) ([dbe405a](https://github.com/raydak-labs/configarr/commit/dbe405a9773e0c199ab87dd18160b2c44456bdce))\n\n## [1.13.5](https://github.com/raydak-labs/configarr/compare/v1.13.4...v1.13.5) (2025-05-03)\n\n### Bug Fixes\n\n- correctly handle possible empty quality arrays ([1ac9912](https://github.com/raydak-labs/configarr/commit/1ac9912f6af01c20604f04a5d795648104fcb80f))\n- correctly order qualities in groups of a profile ([c27b703](https://github.com/raydak-labs/configarr/commit/c27b7031eca10bcd3b09e39f72fb774f3a9bcf0f))\n- **deps:** update dependencies (non-major) ([#257](https://github.com/raydak-labs/configarr/issues/257)) ([27387bb](https://github.com/raydak-labs/configarr/commit/27387bbf07be44fe9d98569693bbec5ab18f9581))\n- improve check for ordering of qualities ([d9ac0ee](https://github.com/raydak-labs/configarr/commit/d9ac0ee21d8f0dd6d85cfa4080fdd86d10b3f5a4))\n- reverse qualities in groups from trash guide ([83479e7](https://github.com/raydak-labs/configarr/commit/83479e771bf4a23175e698eaa64b9f31f962fed8))\n\n### (internal) Refactorings\n\n- remove unnecessary qualities check ([d55c0ec](https://github.com/raydak-labs/configarr/commit/d55c0ec7893b61ec69531285ffd941d05fc5a6a5))\n- simplify starting code ([b969526](https://github.com/raydak-labs/configarr/commit/b9695265801e5fe5e7c3978cd1c10f6118afedcd))\n\n## [1.13.4](https://github.com/raydak-labs/configarr/compare/v1.13.3...v1.13.4) (2025-04-12)\n\n### Bug Fixes\n\n- correctly import quality size / definitions from trash github ([1e2099d](https://github.com/raydak-labs/configarr/commit/1e2099d49a7f044f66dc67fc4f1c178b082dac0f))\n- safe directory, see [#241](https://github.com/raydak-labs/configarr/issues/241) ([d1c237b](https://github.com/raydak-labs/configarr/commit/d1c237b2acbadce8c525d882a5a906cdb1318e68))\n\n## [1.13.3](https://github.com/raydak-labs/configarr/compare/v1.13.2...v1.13.3) (2025-04-03)\n\n### Bug Fixes\n\n- allow all users to access git directory. ([#241](https://github.com/raydak-labs/configarr/issues/241)) ([a9b59da](https://github.com/raydak-labs/configarr/commit/a9b59dac61600963f66c87da8a2544da8dcae1dc)), closes [#240](https://github.com/raydak-labs/configarr/issues/240)\n- **deps:** update dependencies (non-major) ([#238](https://github.com/raydak-labs/configarr/issues/238)) ([5fe6270](https://github.com/raydak-labs/configarr/commit/5fe627082873ca4bdf88e8d494ca4cacdf6d87d9))\n\n## [1.13.2](https://github.com/raydak-labs/configarr/compare/v1.13.1...v1.13.2) (2025-03-23)\n\n### Bug Fixes\n\n- do not fail empty QualityProfiles or CustomFormats are received from server ([d78b441](https://github.com/raydak-labs/configarr/commit/d78b44116aeca689b6543859e711da4b36a8257a)), closes [#230](https://github.com/raydak-labs/configarr/issues/230)\n\n## [1.13.1](https://github.com/raydak-labs/configarr/compare/v1.13.0...v1.13.1) (2025-03-10)\n\n### Bug Fixes\n\n- improve cloning of quality_profiles configs ([89af504](https://github.com/raydak-labs/configarr/commit/89af504f380bc7c21df2ce48a3a8f3748bae23cc)), closes [#223](https://github.com/raydak-labs/configarr/issues/223)\n\n## [1.13.0](https://github.com/raydak-labs/configarr/compare/v1.12.2...v1.13.0) (2025-03-05)\n\n### Features\n\n- support mapping custom formats on unmanaged quality profiles ([cd6dea6](https://github.com/raydak-labs/configarr/commit/cd6dea67c5a7835e68b26255a7f451c73d74720e)), closes [#218](https://github.com/raydak-labs/configarr/issues/218)\n\n### Bug Fixes\n\n- **deps:** update dependencies (non-major) ([#208](https://github.com/raydak-labs/configarr/issues/208)) ([dbe40d1](https://github.com/raydak-labs/configarr/commit/dbe40d12dd7fe5a9a07581f5047a7920640cc8f0))\n\n### (internal) Refactorings\n\n- fix incorrect path in log message ([#215](https://github.com/raydak-labs/configarr/issues/215)) ([ebec0a1](https://github.com/raydak-labs/configarr/commit/ebec0a1a88ed19576a24bdedbabc1f48f8bd5249))\n- fix typo (closes [#202](https://github.com/raydak-labs/configarr/issues/202)) ([1bad256](https://github.com/raydak-labs/configarr/commit/1bad256ffe13bb75431d8175094723bc09ef59f5))\n\n## [1.12.2](https://github.com/raydak-labs/configarr/compare/v1.12.1...v1.12.2) (2025-02-27)\n\n### Bug Fixes\n\n- correctly merge templates and configs ([9235c47](https://github.com/raydak-labs/configarr/commit/9235c47923e3a81da081e73596d018b7c987e9d0))\n\n### (internal) Refactorings\n\n- improve logging for customformatgroups ([f8560df](https://github.com/raydak-labs/configarr/commit/f8560df015b3ee60393461fab3ebd5cacbcff124))\n\n## [1.12.1](https://github.com/raydak-labs/configarr/compare/v1.12.0...v1.12.1) (2025-02-19)\n\n### (internal) Refactorings\n\n- **deps:** update node and pnpm ([8574d3b](https://github.com/raydak-labs/configarr/commit/8574d3b4fb380d3fbbad7bf31a0f90809f0534f6))\n- improve logs ([d611c0e](https://github.com/raydak-labs/configarr/commit/d611c0e40d8166b188904f34cb19c7e1f8831860)), closes [#197](https://github.com/raydak-labs/configarr/issues/197)\n\n## [1.12.0](https://github.com/raydak-labs/configarr/compare/v1.11.0...v1.12.0) (2025-02-10)\n\n### Features\n\n- add support for cleaning up custom formats ([7a28d54](https://github.com/raydak-labs/configarr/commit/7a28d54f678d9231642c02d12c0df58ee97786dd)), closes [#191](https://github.com/raydak-labs/configarr/issues/191)\n- add support for trash custom format groups ([c307377](https://github.com/raydak-labs/configarr/commit/c30737781a3ce47ddd6dafcc978fddcb33137d41)), closes [#185](https://github.com/raydak-labs/configarr/issues/185)\n\n## [1.11.0](https://github.com/raydak-labs/configarr/compare/v1.10.3...v1.11.0) (2025-02-05)\n\n### Features\n\n- add option to disable specific instances in config ([e228ccb](https://github.com/raydak-labs/configarr/commit/e228ccb0820cdb54daf1608280f2a3e0f1304113)), closes [#184](https://github.com/raydak-labs/configarr/issues/184)\n\n### Bug Fixes\n\n- do gracefully stop instance processing if an error occurs. ([8f8fca5](https://github.com/raydak-labs/configarr/commit/8f8fca5013d5abec15b35accf568b44abc17027d))\n\n### (internal) Refactorings\n\n- log total execution summary ([35d0c30](https://github.com/raydak-labs/configarr/commit/35d0c3049c2696a940147468c187380b08274d6f))\n\n## [1.10.3](https://github.com/raydak-labs/configarr/compare/v1.10.2...v1.10.3) (2025-02-03)\n\n### Bug Fixes\n\n- correctly set language for new QualityProfiles ([9ab962b](https://github.com/raydak-labs/configarr/commit/9ab962bd469bb838ba7158594f1f841120890cdb)), closes [#180](https://github.com/raydak-labs/configarr/issues/180)\n- **deps:** pin dependency raw-loader to 4.0.2 ([#171](https://github.com/raydak-labs/configarr/issues/171)) ([1fd8d46](https://github.com/raydak-labs/configarr/commit/1fd8d460f0d331970bc70d53eeac2830be7074d7))\n- **deps:** update dependency docusaurus-lunr-search to v3.6.0 ([#175](https://github.com/raydak-labs/configarr/issues/175)) ([1afcaa2](https://github.com/raydak-labs/configarr/commit/1afcaa2428590509ee94fc8c0d80bd13c3cccb6c))\n\n## [1.10.2](https://github.com/raydak-labs/configarr/compare/v1.10.1...v1.10.2) (2025-01-23)\n\n## [1.10.1](https://github.com/raydak-labs/configarr/compare/v1.10.0...v1.10.1) (2025-01-21)\n\n### Bug Fixes\n\n- use new cfs in existin g qps correctly ([d24aaa6](https://github.com/raydak-labs/configarr/commit/d24aaa6cfc97806a932389f12d41ae4cecc17719)), closes [#164](https://github.com/raydak-labs/configarr/issues/164)\n\n## [1.10.0](https://github.com/raydak-labs/configarr/compare/v1.9.0...v1.10.0) (2025-01-19)\n\n### Features\n\n- add experimental support for cloning quality profiles ([18941ec](https://github.com/raydak-labs/configarr/commit/18941ec259832f291474e5f140fb3f525ed5872c))\n- add experimental support for quality profiles renaming ([504dfe3](https://github.com/raydak-labs/configarr/commit/504dfe3ff4de7df841878d9bf6524201d335ec46))\n\n## [1.9.0](https://github.com/raydak-labs/configarr/compare/v1.8.0...v1.9.0) (2025-01-15)\n\n### Features\n\n- allow configuring quality sizes / definitions with config and templates (alpha) ([f41891b](https://github.com/raydak-labs/configarr/commit/f41891bd87b877cc3c292c737d0dca8e060932d8)), closes [#140](https://github.com/raydak-labs/configarr/issues/140)\n\n### (internal) Refactorings\n\n- ensure all quality definitions always exist if updated ([6aa37cd](https://github.com/raydak-labs/configarr/commit/6aa37cd9eafa6dfa748462508d7c1b0d9679ce0a))\n- ignore empty local templates ([1454670](https://github.com/raydak-labs/configarr/commit/145467033c2282078a41cce98322819032ce340b))\n- restructure trash QD preferred scaling ([53da038](https://github.com/raydak-labs/configarr/commit/53da0381ce928aa743f7f2655b00fc9b40c5349f))\n\n## [1.8.0](https://github.com/raydak-labs/configarr/compare/v1.7.0...v1.8.0) (2025-01-13)\n\n### Features\n\n- add experimental lidarr support ([2093ae0](https://github.com/raydak-labs/configarr/commit/2093ae0fc3fc49b3bb3d77042d33f1ddd45036e4))\n\n### Bug Fixes\n\n- handle local templates as recyclarr templates correctly ([10d7bdb](https://github.com/raydak-labs/configarr/commit/10d7bdb1622ed7b3adbb06bda7d048f113ce24dd))\n\n## [1.7.0](https://github.com/raydak-labs/configarr/compare/v1.6.0...v1.7.0) (2025-01-13)\n\n### Features\n\n- use language from TrashQPs ([7e8981b](https://github.com/raydak-labs/configarr/commit/7e8981b54d5296e1dc9830e4ab7342a948c66fc0))\n\n### (internal) Refactorings\n\n- only build update object for objects with keys ([7abe91c](https://github.com/raydak-labs/configarr/commit/7abe91cb9438fb43ff16d92edb1ae7d2e8693e39))\n\n## [1.6.0](https://github.com/raydak-labs/configarr/compare/v1.5.3...v1.6.0) (2025-01-10)\n\n### Features\n\n- add media_naming compatibility from recyclarr ([3cf73dc](https://github.com/raydak-labs/configarr/commit/3cf73dc2cbbff6fabd1b6e82a9b5b813a307ef26))\n\n### Bug Fixes\n\n- adjust qualityprofile items to always include items key ([9ed86b3](https://github.com/raydak-labs/configarr/commit/9ed86b306c1e8c26bbc4a8c466acf3c84f0240d3))\n- correct customFormatDefinition loading from top level ([f841617](https://github.com/raydak-labs/configarr/commit/f841617c282d0426625824a7b36939df688854ce))\n\n### (internal) Refactorings\n\n- create trash cache for optimization ([d142fee](https://github.com/raydak-labs/configarr/commit/d142feeda4dcccc0649d4c0ce2d65836d23640bc))\n- improve types ([f44eb35](https://github.com/raydak-labs/configarr/commit/f44eb3521401ef44943b71e0921c52e97a3ca2d9))\n\n## [1.5.3](https://github.com/raydak-labs/configarr/compare/v1.5.2...v1.5.3) (2025-01-08)\n\n### Bug Fixes\n\n- allow loading custom format definition correctly from templates ([a5f0f92](https://github.com/raydak-labs/configarr/commit/a5f0f9211b6eac001b9467476aab2a19c93ec6aa))\n\n### (internal) Refactorings\n\n- make CF loading cleaner ([5b33849](https://github.com/raydak-labs/configarr/commit/5b33849e8d30d53e38c2503c33d319035d02b9a0))\n- move merge config to config.ts ([388875d](https://github.com/raydak-labs/configarr/commit/388875dd2240866b071df67d3560e5d461bc2bb7))\n\n## [1.5.2](https://github.com/raydak-labs/configarr/compare/v1.5.1...v1.5.2) (2025-01-03)\n\n### Bug Fixes\n\n- correctly set preferred size value if adjusted by ratio ([5d9dc5c](https://github.com/raydak-labs/configarr/commit/5d9dc5c652f9288063391bb5317f31ad2a9d50dc))\n- **deps:** pin dependency zod to 3.24.1 ([#133](https://github.com/raydak-labs/configarr/issues/133)) ([3773dde](https://github.com/raydak-labs/configarr/commit/3773ddeb7ffecb7bc979d5fa27f6f091f983e983))\n- **deps:** update dependencies (non-major) ([#135](https://github.com/raydak-labs/configarr/issues/135)) ([3ac7b7c](https://github.com/raydak-labs/configarr/commit/3ac7b7c5cad63ac3acc99a3cd95fe3c9854634f6))\n- **deps:** update react monorepo to v19 ([c57a95b](https://github.com/raydak-labs/configarr/commit/c57a95b2394875843d2554d4a1fb910ca32a96be))\n- use quality_defintion from main config if defined ([94d1861](https://github.com/raydak-labs/configarr/commit/94d186160832a2249dfa7626d532a690d91ea72a))\n\n### (internal) Refactorings\n\n- rename variables in code only ([f2f3736](https://github.com/raydak-labs/configarr/commit/f2f37362153bbc09b8633f710b506ed5d26d9db5))\n\n## [1.5.1](https://github.com/raydak-labs/configarr/compare/v1.5.0...v1.5.1) (2024-12-29)\n\n### Bug Fixes\n\n- correctly handle diffs for minFormatScores ([a2494db](https://github.com/raydak-labs/configarr/commit/a2494db839f283d9b0b16b18584b7b745af65e20))\n\n## [1.5.0](https://github.com/raydak-labs/configarr/compare/v1.4.0...v1.5.0) (2024-12-17)\n\n### Features\n\n- add configuration options for media management tab ([c2f2110](https://github.com/raydak-labs/configarr/commit/c2f2110f58f05cd7400ad12f0dc7bf77b0343d3c))\n- add support for loading customformat definitions ([4014d93](https://github.com/raydak-labs/configarr/commit/4014d938f5ab4b747be90c5a65e180941a3dcbdb))\n- optimize envs and add support for custom root for data [#117](https://github.com/raydak-labs/configarr/issues/117) ([f218b56](https://github.com/raydak-labs/configarr/commit/f218b56cf6b43e0508efa1f061223c264985bc1e))\n\n### Bug Fixes\n\n- **deps:** pin dependencies ([#118](https://github.com/raydak-labs/configarr/issues/118)) ([912130a](https://github.com/raydak-labs/configarr/commit/912130a347ff7e06f012778c87a72db20e8aee2b))\n- **deps:** update dependencies (non-major) ([a181831](https://github.com/raydak-labs/configarr/commit/a181831ad10b30b945683a1d0f005a6ed54d64c7))\n- **deps:** update dependency pino-pretty to v13 ([c05c07f](https://github.com/raydak-labs/configarr/commit/c05c07f1241c0c0f47f916071b9c426883b2117e))\n- set default language for new profiles to any ([ffd6faa](https://github.com/raydak-labs/configarr/commit/ffd6faae718df8e13e520db8bf7d4525bcc31d5b))\n\n### (internal) Refactorings\n\n- improve typings for client ([b9ad772](https://github.com/raydak-labs/configarr/commit/b9ad772418fd041f72fd9432d89bb2adf54b083b))\n\n## [1.4.0](https://github.com/raydak-labs/configarr/compare/v1.3.0...v1.4.0) (2024-11-17)\n\n### Features\n\n- add experimental support for readarr ([9085a52](https://github.com/raydak-labs/configarr/commit/9085a5248199bc710187f0a9a8a4c46df43f5083))\n- add expermintal whisparr v3 support ([ff2f08e](https://github.com/raydak-labs/configarr/commit/ff2f08ea551bbd1e57322a82c6705168ac256e73))\n- implement preferredRatio ([b6333db](https://github.com/raydak-labs/configarr/commit/b6333db1a14a4ac68da3c74e43ac9a1a2a15179f))\n- make Trash template / QualityProfiles includable ([5339ced](https://github.com/raydak-labs/configarr/commit/5339ced26ee4f5596f23a10cc93e8879efddc9e6))\n\n### Bug Fixes\n\n- **deps:** update dependencies (non-major) ([cd8b081](https://github.com/raydak-labs/configarr/commit/cd8b081b1432ddbc4f0695859335bee0e33760b1))\n\n### (internal) Refactorings\n\n- adjust some logging ([795ecbd](https://github.com/raydak-labs/configarr/commit/795ecbd2baf9dfbd0a448763b74e838d01c8c904))\n- fix test ([bcfa622](https://github.com/raydak-labs/configarr/commit/bcfa62289913d1ce96a9c1ec675278e80f99e461))\n- improve flow ([738949a](https://github.com/raydak-labs/configarr/commit/738949ae2df841742585a93a510b3c5849623972))\n- improve ky error log ([e68e073](https://github.com/raydak-labs/configarr/commit/e68e073d986e387d362b8826e5d526f97515c35d))\n- move index.ts to src ([b6052e5](https://github.com/raydak-labs/configarr/commit/b6052e5669ac21368635a670fe8e06e7e6c71f07))\n- move types ([98aa2fe](https://github.com/raydak-labs/configarr/commit/98aa2feef8ed0f2579fffeb46c8262e33431c25f))\n- remove tsconfig paths config ([4a34869](https://github.com/raydak-labs/configarr/commit/4a34869e6ba4f25f22180ce20606d7734e55e5a3))\n- rewrite api client usage ([43784ba](https://github.com/raydak-labs/configarr/commit/43784ba3988b9dafb1a65fd63d1b2cc16c8650d9))\n- split config merging and improve types ([2aa101c](https://github.com/raydak-labs/configarr/commit/2aa101cb3f16c70473c4469423eaf97f0425a18c))\n- split local path importer ([e0871ac](https://github.com/raydak-labs/configarr/commit/e0871ac9443007f3bf3f8011870c1f0413022715))\n- unify clone repo ([18c1b69](https://github.com/raydak-labs/configarr/commit/18c1b69b8571dd4dcd7d7973e65fe36a31c403d0))\n- update sonarr/radarr apis ([1dc0d09](https://github.com/raydak-labs/configarr/commit/1dc0d094a0533d61cc078b901b0307602632ddf0))\n- use configureApi ([27aed23](https://github.com/raydak-labs/configarr/commit/27aed239c93b9aab7c5cae773c821f3819b27075))\n"
  },
  {
    "path": "CNAME",
    "content": "configarr.raydak.de\nconfigarr.de\n"
  },
  {
    "path": "DEV.md",
    "content": "# Development related README\n\n## Compiling to binary\n\n### bun (trying)\n\n- compiles and treeshakes code already\n- `bun build src/index.ts --compile --outfile cli-bun`\n- size: 61mb\n\n```bash\n# what could be done but now sure if any real benefit\nbun build src/index.ts \\\n    --compile \\\n    --outfile configarr-${{ matrix.platform }} \\\n    --define CONFIGARR_VERSION=\\\"${{ steps.vars.outputs.version }}\\\" \\\n    --define BUILD_TIME=\\\"${{ steps.vars.outputs.build_time }}\\\" \\\n    --define GITHUB_RUN_ID=\\\"${{ steps.vars.outputs.run_id }}\\\" \\\n    --define GITHUB_REPO=\\\"${{ steps.vars.outputs.repo }}\\\" \\\n    --define GITHUB_SHA=\\\"${{ steps.vars.outputs.sha }}\\\" \\\n    --define BUILD_PLATFORM=\\\"${{ matrix.platform }}\\\" \\\n    --bytecode \\\n    --production \\\n    --minify \\\n    --minify-syntax \\\n    --minify-whitespace \\\n    --minify-identifiers \\\n    ${{ github.event.inputs.baseline == 'true' && '--target=bun' || '' }}\n```\n\n### deno\n\n- `pnpm build`\n- `deno compile --no-check --sloppy-imports --output cli-deno --no-npm --allow-all bundle.cjs`\n- size: 77mb\n\n## docker\n\n- we could probably just use scratch as base? with the binary?\n"
  },
  {
    "path": "Dockerfile",
    "content": "# https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md\n# TODO because multiarch build has problems with QEMU and Node we cannot use alpine here: https://github.com/nodejs/docker-node/issues/1798\nFROM node:24.14.1-slim AS base\nWORKDIR /app\n\nENV PNPM_HOME=\"/opt/pnpm\"\nENV COREPACK_HOME=\"/opt/corepack\"\nENV PATH=\"$PNPM_HOME:$PATH\"\nENV CI=true\n\nRUN apt-get update && apt-get install -y \\\n    git \\\n    && rm -rf /var/lib/apt/lists/*\n\nCOPY package.json pnpm-lock.yaml ./\n\n# Do it here to add the packageManager field to the package.json\nRUN corepack enable \\\n    && corepack prepare pnpm@10 --activate \\\n    && corepack use pnpm@10\n\nRUN pnpm config set store-dir ~/.pnpm-store\n\nRUN --mount=type=cache,id=pnpm,target=~/.pnpm-store pnpm install --frozen-lockfile\n\nFROM base AS builder\nCOPY src src/\nCOPY esbuild.ts ./\n\nARG CONFIGARR_VERSION=dev\nARG BUILD_TIME=\"\"\nARG GITHUB_RUN_ID=\"\"\nARG GITHUB_REPO=\"\"\nARG GITHUB_SHA=\"\"\n\nENV CONFIGARR_VERSION=${CONFIGARR_VERSION}\nENV BUILD_TIME=${BUILD_TIME}\nENV GITHUB_RUN_ID=${GITHUB_RUN_ID}\nENV GITHUB_REPO=${GITHUB_REPO}\nENV GITHUB_SHA=${GITHUB_SHA}\n\nRUN pnpm run build\n\nFROM base AS dev\n# manually mount src etc\n\nARG CONFIGARR_VERSION=dev\nENV CONFIGARR_VERSION=${CONFIGARR_VERSION}\n\nCMD [ \"pnpm\", \"start\" ]\n# https://github.com/evanw/esbuild/issues/1921\nFROM node:24.14.1-alpine AS prod\nWORKDIR /app\n\nRUN apk add --no-cache libstdc++ dumb-init git\n\n# Allow global git access independent of container user and directory user. See #240, #241\nRUN git config --global --add safe.directory '*'\n\n# TODO maybe in future. Results in breaking change\n#USER node\n\nCOPY --from=builder /app/bundle.cjs /app/index.js\n\nARG CONFIGARR_VERSION=dev\nENV CONFIGARR_VERSION=${CONFIGARR_VERSION}\n# Run with dumb-init to not start node with PID=1, since Node.js was not designed to run as PID 1\nCMD [\"dumb-init\", \"node\", \"index.js\"]\n\n# Compatibility target pinned to Alpine 3.22.\n# Useful for testing old-kernel behavior with a fixed Alpine/git stack.\nFROM node:24.14.1-alpine3.22 AS prod-alpine-3-22\nWORKDIR /app\n\nRUN apk add --no-cache libstdc++ dumb-init git\n\n# Allow global git access independent of container user and directory user. See #240, #241\nRUN git config --global --add safe.directory '*'\n\nCOPY --from=builder /app/bundle.cjs /app/index.js\n\nARG CONFIGARR_VERSION=dev\nENV CONFIGARR_VERSION=${CONFIGARR_VERSION}\nCMD [\"dumb-init\", \"node\", \"index.js\"]\n"
  },
  {
    "path": "Dockerfile-deno.Dockerfile",
    "content": "# https://github.com/denoland/deno_docker/blob/main/example/Dockerfile\n\nFROM denoland/deno:alpine-2.0.0 AS base\nWORKDIR /app\n\nRUN apk add --no-cache libstdc++ dumb-init git\n\nCOPY package.json pnpm-lock.yaml ./\n\nRUN deno install\n\nFROM base AS builder\nCOPY src src/\nCOPY index.ts esbuild.ts ./\n\nRUN deno --allow-env --allow-read --allow-run esbuild.ts\n\nFROM base AS dev\nENV DENO_DIR=/app/.deno_cache\n# manually mount src etc\n\nCMD [\"deno\", \"--allow-env\", \"--allow-read\", \"--allow-run\", \"--allow-net\", \"--allow-sys\", \"--unstable-sloppy-imports\", \"index.ts\"]\n\n# https://github.com/evanw/esbuild/issues/1921\nFROM denoland/deno:alpine-2.0.0 AS prod\nWORKDIR /app\n\nRUN apk add --no-cache libstdc++ dumb-init git\n\n# TODO maybe in future. Results in breaking change\n#USER node\n\nCOPY --from=builder /app/bundle.cjs /app/index.cjs\n\nENV DENO_DIR=/app/.deno_cache\n\n# Compile cache / modify for multi-user\nRUN deno cache --unstable-sloppy-imports index.cjs || true\nRUN chmod uga+rw -R ${DENO_DIR}\n\n# Not sure about those options\n#--cached-only\n#--no-code-cache\n\n# TODO not sure about this\n# Run with dumb-init to not start node with PID=1, since Node.js was not designed to run as PID 1\nCMD [\"dumb-init\", \"deno\", \"--allow-env\", \"--allow-read\", \"--allow-run\", \"--allow-net\", \"--allow-sys\", \"--unstable-sloppy-imports\", \"index.cjs\"]\n\n\n# BUN Sample\n# FROM oven/bun:1.1.30-alpine AS prod\n# WORKDIR /app\n\n# RUN apk add --no-cache libstdc++ dumb-init git\n\n# # TODO maybe in future. Results in breaking change\n# #USER node\n\n# COPY --from=builder /app/bundle.cjs /app/index.cjs\n\n# ENV CONFIG_LOCATION=/app/config/config.yml\n# ENV SECRETS_LOCATION=/app/config/secrets.yml\n\n# # Run with dumb-init to not start node with PID=1, since Node.js was not designed to run as PID 1\n# CMD [\"dumb-init\", \"bun\", \"index.cjs\"]\n"
  },
  {
    "path": "GEMINI.md",
    "content": "## General\n\nYou are a TypeScript expert specializing in modern TS and async programming.\n\n## Focus Areas\n\n- ES6+ features (destructuring, modules, classes)\n- Async patterns (promises, async/await, generators)\n- Event loop and microtask queue understanding\n- Node.js APIs and performance optimization\n- Browser APIs and cross-browser compatibility\n- TypeScript migration and type safety\n\n## Approach\n\n1. Prefer async/await over promise chains\n2. Use functional patterns where appropriate\n3. Handle errors at appropriate boundaries\n4. Avoid callback hell with modern patterns\n5. Consider bundle size for browser code\n6. Try adding types where needed. Avoid using any.\n\n## Output\n\n- Modern TypeScript with proper error handling\n- Async code with race condition prevention\n- Module structure with clean exports\n- Jest tests with async test patterns\n\n## Project IMPORTANT\n\n- source code is located in `src`\n- This is a software for synching external tools: `sonarr, radarr, ...` with a unified configuration\n- Sample confguration file is here `examples/full/config/config.yml`\n- The external api endpoint are automatically generated and stored here `src/__generated__` (only look into it when required)\n  - A merged configuration per client is here `src/clients`\n- After you are done run `pnpm lint:fix`\n- In the end the following commands should run without error:\n  - `pnpm build`\n  - `pnpm lint`\n  - `pnpm typecheck`\n  - `pnpm test`\n\n## Useful Command-Line Tools\n\n- `jq` for interacting with json\n- `rg` (ripgrep) command is available for fast searches in text files.\n- `fzf` for fuzzy finding\n- `git` for interacting with git repos\n- `fd` for faster finds\n\n## Regarding Dependencies:\n\n- Avoid introducing new external dependencies unless absolutely necessary.\n- If a new dependency is required, please state the reason.\n"
  },
  {
    "path": "LICENSE",
    "content": "GNU AFFERO GENERAL PUBLIC LICENSE\nVersion 3, 19 November 2007\n\nCopyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\nEveryone is permitted to copy and distribute verbatim copies\nof this license document, but changing it is not allowed.\n\n     Preamble\n\nThe GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\nWhen we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nDevelopers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\nA secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\nThe GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\nAn older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\nTERMS AND CONDITIONS\n\n0. Definitions.\n\n\"This License\" refers to version 3 of the GNU Affero General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\nThe Corresponding Source for a work in source code form is that\nsame work.\n\n2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\na) The work must carry prominent notices stating that you modified\nit, and giving a relevant date.\n\nb) The work must carry prominent notices stating that it is\nreleased under this License and any conditions added under section\n7.  This requirement modifies the requirement in section 4 to\n\"keep intact all notices\".\n\nc) You must license the entire work, as a whole, under this\nLicense to anyone who comes into possession of a copy.  This\nLicense will therefore apply, along with any applicable section 7\nadditional terms, to the whole of the work, and all its parts,\nregardless of how they are packaged.  This License gives no\npermission to license the work in any other way, but it does not\ninvalidate such permission if you have separately received it.\n\nd) If the work has interactive user interfaces, each must display\nAppropriate Legal Notices; however, if the Program has interactive\ninterfaces that do not display Appropriate Legal Notices, your\nwork need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n6. Conveying Non-Source Forms.\n\nYou may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\na) Convey the object code in, or embodied in, a physical product\n(including a physical distribution medium), accompanied by the\nCorresponding Source fixed on a durable physical medium\ncustomarily used for software interchange.\n\nb) Convey the object code in, or embodied in, a physical product\n(including a physical distribution medium), accompanied by a\nwritten offer, valid for at least three years and valid for as\nlong as you offer spare parts or customer support for that product\nmodel, to give anyone who possesses the object code either (1) a\ncopy of the Corresponding Source for all the software in the\nproduct that is covered by this License, on a durable physical\nmedium customarily used for software interchange, for a price no\nmore than your reasonable cost of physically performing this\nconveying of source, or (2) access to copy the\nCorresponding Source from a network server at no charge.\n\nc) Convey individual copies of the object code with a copy of the\nwritten offer to provide the Corresponding Source.  This\nalternative is allowed only occasionally and noncommercially, and\nonly if you received the object code with such an offer, in accord\nwith subsection 6b.\n\nd) Convey the object code by offering access from a designated\nplace (gratis or for a charge), and offer equivalent access to the\nCorresponding Source in the same way through the same place at no\nfurther charge.  You need not require recipients to copy the\nCorresponding Source along with the object code.  If the place to\ncopy the object code is a network server, the Corresponding Source\nmay be on a different server (operated by you or a third party)\nthat supports equivalent copying facilities, provided you maintain\nclear directions next to the object code saying where to find the\nCorresponding Source.  Regardless of what server hosts the\nCorresponding Source, you remain obligated to ensure that it is\navailable for as long as needed to satisfy these requirements.\n\ne) Convey the object code using peer-to-peer transmission, provided\nyou inform other peers where the object code and Corresponding\nSource of the work are being offered to the general public at no\ncharge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\na) Disclaiming warranty or limiting liability differently from the\nterms of sections 15 and 16 of this License; or\n\nb) Requiring preservation of specified reasonable legal notices or\nauthor attributions in that material or in the Appropriate Legal\nNotices displayed by works containing it; or\n\nc) Prohibiting misrepresentation of the origin of that material, or\nrequiring that modified versions of such material be marked in\nreasonable ways as different from the original version; or\n\nd) Limiting the use for publicity purposes of names of licensors or\nauthors of the material; or\n\ne) Declining to grant rights under trademark law for use of some\ntrade names, trademarks, or service marks; or\n\nf) Requiring indemnification of licensors and authors of that\nmaterial by anyone who conveys the material (or modified versions of\nit) with contractual assumptions of liability to the recipient, for\nany liability that these contractual assumptions directly impose on\nthose licensors and authors.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n13. Remote Network Interaction; Use with the GNU General Public License.\n\nNotwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n14. Revised Versions of this License.\n\nThe Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\nIf the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\nLater license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n\nHow to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\nTo do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n<one line to give the program's name and a brief idea of what it does.>\nCopyright (C) <year>  <name of author>\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published\nby the Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU Affero General Public License for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\nYou should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "README.md",
    "content": "# Configarr\n\n[![Documentation](https://img.shields.io/badge/Documentation-blue)](https://configarr.de)\n[![GitHub License](https://img.shields.io/github/license/raydak-labs/configarr)](https://github.com/raydak-labs/configarr/blob/main/LICENSE)\n[![GitHub Release](https://img.shields.io/github/v/release/raydak-labs/configarr?logo=github)](https://github.com/raydak-labs/configarr/releases/)\n[![Docker](https://img.shields.io/docker/v/configarr/configarr?sort=semver&label=DockerHub)](https://hub.docker.com/r/configarr/configarr)\n[![Docker Pulls](https://img.shields.io/docker/pulls/configarr/configarr?label=DockerHub-Pulls)](https://hub.docker.com/r/configarr/configarr)\n\n<p align=\"center\" >\n  <img width=\"150\"  src=\"./docs/static/img/logo.webp\" alt=\"Logo of configarr\" />\n</p>\n\nConfigarr is an open-source tool designed to simplify configuration and synchronization for Sonarr and Radarr (and other experimental).\nIt integrates with TRaSH Guides to automate updates of custom formats, quality profiles, and other settings, while also supporting user-defined configurations.\nConfigarr offers flexibility with deployment options like Docker and Kubernetes, ensuring compatibility with the latest versions of Sonarr and Radarr.\nBy streamlining media server management, it saves time, enhances consistency, and reduces manual intervention.\n\nOfficial support only for Sonarr v4 and radarr v5.\n\nThis will be a project similar to [Recyclarr](https://github.com/recyclarr/recyclarr) or [Notifiarr](https://notifiarr.wiki/) but support for additional specification and not only what [TRaSH-Guides](https://trash-guides.info/) offer.\n\n## Container Images\n\nImages are available via Github Container Registry (ghcr) and dockerhub with tags and latest (check the repos for all available tags):\n\n- [GHCR:](https://github.com/raydak-labs/configarr/pkgs/container/configarr)\n  - `ghcr.io/raydak-labs/configarr:latest`\n- [DockerHub:](https://hub.docker.com/repository/docker/configarr/configarr/general)\n  - `docker.io/configarr/configarr:latest` or simply `configarr/configarr:latest`\n\n## Features\n\n- Use TRaSH-Guides defined custom formats\n- Compatible with recyclarr templates\n- Include own defined custom formats\n- Custom defined formats for different languages/countries like Germany\n- Support all CustomFormat specifications\n- Provide CFs in different ways\n  - Sync from TRaSH-Guides\n  - Sync with local file CFs\n  - Provide CFs directly in config (Convert JSON with https://www.bairesdev.com/tools/json2yaml/)\n  - Merge order is `TRaSH-Guides -> LocalFiles -> CFs in Config`\n\n### Supported \\*arr Applications\n\n- Radarr v5\n- Sonarr v4\n- Whisparr (experimental)\n- Readarr (experimental)\n- Lidarr (experimental)\n\n## Configuration\n\nFull documentation can be found here: [Documentation](https://configarr.de)\n\n- `config.yml`\n  - Check the template file [template](./config.yml.template) or check the examples.\n  - You can provide values with the custom tags:\n    - `value: !secret secretKey`: Loads the value from the secrets file with the key `secretKey`\n    - `value: !env ENV_NAME`: Loads the value from the environment variable `ENV_NAME`\n    - `value: !file FILE_NAME`: Loads the value from the file `FILE_NAME`\n      - Make sure Configarr has enough permissions to read the file\n\n## Custom formats\n\nThis repository also provide additional custom formats what TRaSH-Guides does not offer.\n\nSee [here](./custom/cfs/)\n\n- Most CustomFormats used from @PCJones\n  - See here: https://github.com/PCJones/radarr-sonarr-german-dual-language\n  - Or good german guide: https://github.com/PCJones/usenet-guide\n\n## Development\n\n1. Optionally setup the local sonarr instance\n   1. Run `docker compose up -d` to run the container\n   2. Open sonarr in your browser at http://localhost:8989 / radarr @ http://localhost:7878\n   3. Configure basic authentication, disable local authentication and create an initial user by specifying the e-mail and password\n2. Open the sonarr [Settings > General](http://localhost:8989/settings/general) page and copy the API key\n3. Create a `secrets.yml` from the template\n   1. `cp secrets.yml.template secrets.yml`\n   2. Replace the placeholder with your sonarr API key\n4. Create a `config.yml` from the template\n   1. `cp config.yml.template config.yml`\n   2. Overwrite the hosts in case you are not using the local setup with docker compose\n5. Run the app with `pnpm start` or with the vscode task\n\nTip: Beside from those steps you can also always test and work with the `full example` and utilizing the docker container with mounted `src` directory.\n\n## Examples\n\nSome examples for configuration are provided [Examples](./examples/)\n\n## Related projects\n\nYou can compare features here: [Feature Comparison](https://configarr.de/docs/comparison)\n\n- https://github.com/recyclarr/recyclarr\n- https://notifiarr.wiki/\n- https://github.com/Dictionarry-Hub/profilarr\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=raydak-labs/configarr&type=Date)](https://www.star-history.com/#raydak-labs/configarr&Date)\n"
  },
  {
    "path": "config.yml.template",
    "content": "trashGuideUrl: https://github.com/TRaSH-Guides/Guides\n#trashRevision: master # Optional to specify sha\nrecyclarrConfigUrl: https://github.com/recyclarr/config-templates\n#recyclarrRevision: master # Optional to specify sha\n\n# Optional if you want to add custom formats locally\n#localCustomFormatsPath: ./custom/cfs\n#localConfigTemplatesPath: /app/templates\n\nsonarr:\n  series:\n    # Set the URL/API Key to your actual instance\n    base_url: http://localhost:8989\n    api_key: !secret SONARR_API_KEY\n\n    # Quality definitions from the guide to sync to Sonarr. Choices: series, anime\n    quality_definition:\n      type: series\n\n    include:\n      # Comment out any of the following includes to disable them\n      #### WEB-1080p\n      - template: sonarr-quality-definition-series\n      - template: sonarr-v4-quality-profile-web-1080p\n      - template: sonarr-v4-custom-formats-web-1080p\n\n      #### WEB-2160p\n      - template: sonarr-v4-quality-profile-web-2160p\n      - template: sonarr-v4-custom-formats-web-2160p\n\n    # Custom Formats: https://recyclarr.dev/wiki/yaml/config-reference/custom-formats/\n    custom_formats: []\n\nradarr:\n  instance1:\n    define: true\n"
  },
  {
    "path": "custom/cfs/custom-size-bigger-40gb.json",
    "content": "{\n  \"trash_id\": \"custom-size-more-40gb\",\n  \"trash_scores\": {\n    \"default\": -10000\n  },\n  \"trash_description\": \"Size: Block sizes over 40GB\",\n  \"name\": \"Size: Block More 40GB\",\n  \"includeCustomFormatWhenRenaming\": false,\n  \"specifications\": [\n    {\n      \"name\": \"Size\",\n      \"implementation\": \"SizeSpecification\",\n      \"negate\": true,\n      \"required\": true,\n      \"fields\": {\n        \"min\": 0,\n        \"max\": 40\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "custom/cfs/de-tier-01.json",
    "content": "{\n  \"trash_id\": \"de-tier-01-version1\",\n  \"trash_scores\": {},\n  \"name\": \"GER HD Rel. Group Tier 01\",\n  \"includeCustomFormatWhenRenaming\": false,\n  \"specifications\": [\n    {\n      \"name\": \"ZeroTwo\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"ZeroTwo\"\n      }\n    },\n    {\n      \"name\": \"WAYNE\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"WAYNE\"\n      }\n    },\n    {\n      \"name\": \"TSCC\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"TSCC\"\n      }\n    },\n    {\n      \"name\": \"DETAiLS\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"DETAiLS\"\n      }\n    },\n    {\n      \"name\": \"FoST\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"FoST\"\n      }\n    },\n    {\n      \"name\": \"SAUERKRAUT\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"SAUERKRAUT\"\n      }\n    },\n    {\n      \"name\": \"Baka (Anime)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"Baka\"\n      }\n    },\n    {\n      \"name\": \"D02KU\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"D02KU\"\n      }\n    },\n    {\n      \"name\": \"WvF\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"WvF\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "custom/cfs/de-tier-02.json",
    "content": "{\n  \"trash_id\": \"de-tier-02-version1\",\n  \"trash_scores\": {},\n  \"name\": \"GER HD Rel. Group Tier 02\",\n  \"includeCustomFormatWhenRenaming\": false,\n  \"specifications\": [\n    {\n      \"name\": \"iNTENTiON\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"iNTENTiON\"\n      }\n    },\n    {\n      \"name\": \"TVS (keine ENG subs?)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"TVS\"\n      }\n    },\n    {\n      \"name\": \"TV4A (check ob Tier 01)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"TV4A\"\n      }\n    },\n    {\n      \"name\": \"TvR\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"TvR\"\n      }\n    },\n    {\n      \"name\": \"TVARCHiV\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"TVARCHiV\"\n      }\n    },\n    {\n      \"name\": \"RUBBiSH\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"RUBBiSH\"\n      }\n    },\n    {\n      \"name\": \"MGE\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"MGE\"\n      }\n    },\n    {\n      \"name\": \"HAXE\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"HAXE\"\n      }\n    },\n    {\n      \"name\": \"RSG\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"RSG\"\n      }\n    },\n    {\n      \"name\": \"AWARDS (Inactive)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"AWARDS\"\n      }\n    },\n    {\n      \"name\": \"TMSF\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"TMSF\"\n      }\n    },\n    {\n      \"name\": \"HQC (Inactive)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"HQC\"\n      }\n    },\n    {\n      \"name\": \"DMPD\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"DMPD\"\n      }\n    },\n    {\n      \"name\": \"MiSFiTS (Inactive)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"MiSFiTS\"\n      }\n    },\n    {\n      \"name\": \"4SJ (Inactive)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"4SJ\"\n      }\n    },\n    {\n      \"name\": \"euHD (Inactive)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"euHD\"\n      }\n    },\n    {\n      \"name\": \"FuN (Low Size 265)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"FuN\"\n      }\n    },\n    {\n      \"name\": \"OCA (Inactive)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"OCA\"\n      }\n    },\n    {\n      \"name\": \"JaJunge\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"JaJunge\"\n      }\n    },\n    {\n      \"name\": \"RWP\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"RWP\"\n      }\n    },\n    {\n      \"name\": \"WOTT\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"WOTT\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "custom/cfs/de-tier-03.json",
    "content": "{\n  \"trash_id\": \"de-tier-03-version1\",\n  \"trash_scores\": {},\n  \"name\": \"GER HD Rel. Group Tier 03\",\n  \"includeCustomFormatWhenRenaming\": false,\n  \"specifications\": [\n    {\n      \"name\": \"AIDA\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"AIDA\"\n      }\n    },\n    {\n      \"name\": \"SunDry\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"SunDry\"\n      }\n    },\n    {\n      \"name\": \"TVP (Inactive)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"TVP\"\n      }\n    },\n    {\n      \"name\": \"UTOPiA\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"UTOPiA\"\n      }\n    },\n    {\n      \"name\": \"VECTOR (Evaluate 07.2024)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"VECTOR\"\n      }\n    },\n    {\n      \"name\": \"EXCiTED (Inactive)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"EXCiTED\"\n      }\n    },\n    {\n      \"name\": \"FRAGGERS (Inactive)\",\n      \"implementation\": \"ReleaseGroupSpecification\",\n      \"negate\": false,\n      \"required\": false,\n      \"fields\": {\n        \"value\": \"FRAGGERS\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "custom/cfs/lang-de-dl-2.json",
    "content": "{\n  \"trash_id\": \"lang-de-dl-2-version1\",\n  \"trash_scores\": {},\n  \"name\": \"German DL 2\",\n  \"includeCustomFormatWhenRenaming\": false,\n  \"specifications\": [\n    {\n      \"name\": \"NOT German DL\",\n      \"implementation\": \"ReleaseTitleSpecification\",\n      \"negate\": true,\n      \"required\": true,\n      \"fields\": {\n        \"value\": \"(?i)german\\\\s*\\\\.?dl|(?<=\\\\bGerman\\\\b.*)(?<!\\\\bWEB[-_. ])\\\\bDL\\\\b|\\\\[DE\\\\+[a-z]{2}\\\\]|\\\\[[a-z]{2}\\\\+DE\\\\]|ger,\\\\s*[a-z]{3}\\\\]|\\\\[[a-z]{3}\\\\s*,\\\\s*ger\\\\]\"\n      }\n    },\n    {\n      \"name\": \"German\",\n      \"implementation\": \"LanguageSpecification\",\n      \"negate\": false,\n      \"required\": true,\n      \"fields\": {\n        \"value\": 4\n      }\n    },\n    {\n      \"name\": \"English\",\n      \"implementation\": \"LanguageSpecification\",\n      \"negate\": false,\n      \"required\": true,\n      \"fields\": {\n        \"value\": 1\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "custom/cfs/lang-de-dl.json",
    "content": "{\n  \"trash_id\": \"lang-de-dl-version1\",\n  \"trash_scores\": {},\n  \"name\": \"German DL\",\n  \"includeCustomFormatWhenRenaming\": true,\n  \"specifications\": [\n    {\n      \"name\": \"German DL\",\n      \"implementation\": \"ReleaseTitleSpecification\",\n      \"negate\": false,\n      \"required\": true,\n      \"fields\": {\n        \"value\": \"(?i)german\\\\s*\\\\.?dl|(?<=\\\\bGerman\\\\b.*)(?<!\\\\bWEB[-_. ])\\\\bDL\\\\b|\\\\[DE\\\\+[a-z]{2}\\\\]|\\\\[[a-z]{2}\\\\+DE\\\\]|ger,\\\\s*[a-z]{3}\\\\]|\\\\[[a-z]{3}\\\\s*,\\\\s*ger\\\\]\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "custom/cfs/lang-de-only.json",
    "content": "{\n  \"trash_id\": \"lang-de-only-version1\",\n  \"trash_scores\": {},\n  \"name\": \"Language: German Only\",\n  \"includeCustomFormatWhenRenaming\": false,\n  \"specifications\": [\n    {\n      \"name\": \"Language GER\",\n      \"implementation\": \"LanguageSpecification\",\n      \"negate\": false,\n      \"required\": true,\n      \"fields\": {\n        \"value\": 4\n      }\n    },\n    {\n      \"name\": \"NOT German DL\",\n      \"implementation\": \"ReleaseTitleSpecification\",\n      \"negate\": true,\n      \"required\": true,\n      \"fields\": {\n        \"value\": \"(?i)german\\\\s*\\\\.?dl|(?<=\\\\bGerman\\\\b.*)(?<!\\\\bWEB[-_. ])\\\\bDL\\\\b|\\\\[DE\\\\+[a-z]{2}\\\\]|\\\\[[a-z]{2}\\\\+DE\\\\]|ger,\\\\s*[a-z]{3}\\\\]|\\\\[[a-z]{3}\\\\s*,\\\\s*ger\\\\]\"\n      }\n    },\n    {\n      \"name\": \"Not English\",\n      \"implementation\": \"LanguageSpecification\",\n      \"negate\": true,\n      \"required\": true,\n      \"fields\": {\n        \"value\": 1\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "custom/cfs/lang-en-only.json",
    "content": "{\n  \"trash_id\": \"lang-en-only-version1\",\n  \"trash_scores\": {},\n  \"name\": \" Language: English Only\",\n  \"includeCustomFormatWhenRenaming\": false,\n  \"specifications\": [\n    {\n      \"name\": \"Language ENG\",\n      \"implementation\": \"LanguageSpecification\",\n      \"negate\": false,\n      \"required\": true,\n      \"fields\": {\n        \"value\": 1\n      }\n    },\n    {\n      \"name\": \"NOT German DL\",\n      \"implementation\": \"ReleaseTitleSpecification\",\n      \"negate\": true,\n      \"required\": true,\n      \"fields\": {\n        \"value\": \"(?i)german\\\\s*\\\\.?dl|(?<=\\\\bGerman\\\\b.*)(?<!\\\\bWEB[-_. ])\\\\bDL\\\\b|\\\\[DE\\\\+[a-z]{2}\\\\]|\\\\[[a-z]{2}\\\\+DE\\\\]|ger,\\\\s*[a-z]{3}\\\\]|\\\\[[a-z]{3}\\\\s*,\\\\s*ger\\\\]\"\n      }\n    },\n    {\n      \"name\": \"NOT German Language\",\n      \"implementation\": \"LanguageSpecification\",\n      \"negate\": true,\n      \"required\": true,\n      \"fields\": {\n        \"value\": 4\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "custom/cfs/lang-mic-dub.json",
    "content": "{\n  \"trash_id\": \"lang-mic-dub-version1\",\n  \"trash_scores\": {},\n  \"name\": \"MIC DUB\",\n  \"includeCustomFormatWhenRenaming\": true,\n  \"specifications\": [\n    {\n      \"name\": \"MD\",\n      \"implementation\": \"ReleaseTitleSpecification\",\n      \"negate\": false,\n      \"required\": true,\n      \"fields\": {\n        \"value\": \"((19|20|21)[0-9]{2}).*?(?<=^|[ .-])MD(?=[ .-]|$).*?(?<=^|[ .-])GERMAN(?=[ .-]|$)|((19|20|21)[0-9]{2}).*?(?<=^|[ .-])GERMAN(?=[ .-]|$).*?(?<=^|[ .-])MD(?=[ .-]|$)\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "custom/cfs/lang-not-en-de.json",
    "content": "{\n  \"trash_id\": \"lang-not-en-de-version1\",\n  \"trash_scores\": {},\n  \"name\": \"Language: Not ENG/GER\",\n  \"includeCustomFormatWhenRenaming\": false,\n  \"specifications\": [\n    {\n      \"name\": \"Not English Language\",\n      \"implementation\": \"LanguageSpecification\",\n      \"negate\": true,\n      \"required\": true,\n      \"fields\": {\n        \"value\": 1\n      }\n    },\n    {\n      \"name\": \"Not German Language\",\n      \"implementation\": \"LanguageSpecification\",\n      \"negate\": true,\n      \"required\": true,\n      \"fields\": {\n        \"value\": 4\n      }\n    },\n    {\n      \"name\": \"Not German in Title\",\n      \"implementation\": \"ReleaseTitleSpecification\",\n      \"negate\": true,\n      \"required\": true,\n      \"fields\": {\n        \"value\": \"(?i)\\\\bgerman\\\\b\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Configarr documentation\n\nThis website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.\n\n### Installation\n\n```\n$ pnpm\n```\n\n### Local Development\n\n```\n$ pnpm start\n```\n\nThis command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.\n\n### Build\n\n```\n$ pnpm build\n```\n\nThis command generates static content into the `build` directory and can be served using any static contents hosting service.\n\n### Deployment\n\nUsing SSH:\n\n```\n$ USE_SSH=true pnpm deploy\n```\n\nNot using SSH:\n\n```\n$ GIT_USER=<Your GitHub username> pnpm deploy\n```\n\nIf you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.\n"
  },
  {
    "path": "docs/docs/changelog.mdx",
    "content": "---\nsidebar_position: 8\ndescription: \"Changelog / Version changes\"\nkeywords: [changelog, version]\ntitle: Changelog\n---\n\nimport CodeBlock from \"@theme/CodeBlock\";\nimport { default as CHANGELOG } from \"../../CHANGELOG.md\";\n\n#\n\n<CHANGELOG components={props.components} />\n"
  },
  {
    "path": "docs/docs/comparison.mdx",
    "content": "---\nsidebar_position: 7\ndescription: \"Comparison of Configarr to other tools\"\nkeywords: [comparison, recyclarr, notifiarr]\n---\n\n# Comparison\n\nAs Configarr is not the only tool around we will compare different tools based on feature sets in this sections.\nThe main focus of Configarr is the full support of TRaSH-Guide and combine it with maximum customizability by the user.\nThat's also the most mentionable contrast to all other tools which do not provide this flexibility as shown in the matrix.\nFocused together for seamless experience in containerized environments with docker and kubernetes.\n\nNotes:\n\n- TRaSH-Guide prefix means that we support the TRaSH-Guide provided JSON files in the repository [Repo](https://github.com/TRaSH-Guides/Guides/tree/master/docs/json)\n- Custom prefix means that you can create own e.q. custom formats in the tools which are not defined in TRaSH or somewhere.\n  The user can do it directly with the tool in the config.\n- CustomFormatGroups are support like it makes sense.\n  They are mostly create for interactive purpose which does not makes much sense with Configarr.\n  Default will be loading all required one's and user can enable to load all with a flag.\n  Recommendation is to load optional ones with `custom_formats`\n\n## Radarr\n\n| Features                         | <div class=\"configarrHeader\">Configarr</div> |     Notifiarr      |     Recyclarr      |\n| -------------------------------- | :------------------------------------------: | :----------------: | :----------------: |\n| GUI (graphical user interface)   |                                              | :white_check_mark: |                    |\n| TRaSH-Guide Custom Formats       |              :white_check_mark:              | :white_check_mark: | :white_check_mark: |\n| TRaSH-Guide Custom Format Groups |              :white_check_mark:              | :white_check_mark: |                    |\n| TRaSH-Guide Quality Profiles     |              :white_check_mark:              | :white_check_mark: |                    |\n| TRaSH-Guide Quality Sizes        |              :white_check_mark:              | :white_check_mark: | :white_check_mark: |\n| TRaSH-Guide Naming               |              :white_check_mark:              | :white_check_mark: | :white_check_mark: |\n| Custom Custom Formats            |              :white_check_mark:              |                    |                    |\n| Custom Quality Profiles          |              :white_check_mark:              |     :memo: \\*1     | :white_check_mark: |\n| Custom Quality Sizes             |              :white_check_mark:              |                    |                    |\n| Custom Naming                    |              :white_check_mark:              |                    |                    |\n| Clear all Custom Formats         |              :white_check_mark:              | :white_check_mark: | :white_check_mark: |\n| Modify Scores                    |              :white_check_mark:              | :white_check_mark: | :white_check_mark: |\n| Profile renaming                 |              :white_check_mark:              | :white_check_mark: |                    |\n| Profile cloning                  |              :white_check_mark:              |                    |                    |\n\n<div className=\"comparisonTableNote\">\n  \\*1: You start with one of the provided templates and can adjust things. But not just start with a clean QualityProfile.\n</div>\n\n## Sonarr\n\n| Features                         | <div class=\"configarrHeader\">Configarr</div> |     Notifiarr      |     Recyclarr      |\n| -------------------------------- | :------------------------------------------: | :----------------: | :----------------: |\n| GUI (graphical user interface)   |                                              | :white_check_mark: |                    |\n| TRaSH-Guide Custom Formats       |              :white_check_mark:              | :white_check_mark: | :white_check_mark: |\n| TRaSH-Guide Custom Format Groups |              :white_check_mark:              | :white_check_mark: |                    |\n| TRaSH-Guide Quality Profiles     |              :white_check_mark:              | :white_check_mark: |                    |\n| TRaSH-Guide Quality Sizes        |              :white_check_mark:              | :white_check_mark: | :white_check_mark: |\n| TRaSH-Guide Naming               |              :white_check_mark:              | :white_check_mark: | :white_check_mark: |\n| Custom Custom Formats            |              :white_check_mark:              |                    |                    |\n| Custom Quality Profiles          |              :white_check_mark:              |     :memo: \\*1     | :white_check_mark: |\n| Custom Quality Sizes             |              :white_check_mark:              |                    |                    |\n| Custom Naming                    |              :white_check_mark:              |                    |                    |\n| Clear all Custom Formats         |              :white_check_mark:              | :white_check_mark: | :white_check_mark: |\n| Modify Scores                    |              :white_check_mark:              | :white_check_mark: | :white_check_mark: |\n| Profile renaming                 |              :white_check_mark:              | :white_check_mark: |                    |\n| Profile cloning                  |              :white_check_mark:              |                    |                    |\n\n<div className=\"comparisonTableNote\">\n  \\*1: You start with one of the provided templates and can adjust things. But not just start with a clean QualityProfile.\n</div>\n\n## Whisparr\n\n| Features                       | <div class=\"configarrHeader\">Configarr</div> | Notifiarr (unsupported) | Recyclarr (unsupported) |\n| ------------------------------ | :------------------------------------------: | :---------------------: | :---------------------: |\n| GUI (graphical user interface) |                                              |                         |                         |\n| Custom Custom Formats          |              :white_check_mark:              |                         |                         |\n| Custom Quality Profiles        |              :white_check_mark:              |                         |                         |\n| Custom Quality Sizes           |              :white_check_mark:              |                         |                         |\n| Custom Naming                  |              :white_check_mark:              |                         |                         |\n| Clear all Custom Formats       |              :white_check_mark:              |                         |                         |\n| Modify Scores                  |              :white_check_mark:              |                         |                         |\n| Profile renaming               |              :white_check_mark:              |                         |                         |\n| Profile cloning                |              :white_check_mark:              |                         |                         |\n\n## Readarr\n\n| Features                       | <div class=\"configarrHeader\">Configarr</div> | Notifiarr (unsupported) | Recyclarr (unsupported) |\n| ------------------------------ | :------------------------------------------: | :---------------------: | :---------------------: |\n| GUI (graphical user interface) |                                              |                         |                         |\n| Custom Custom Formats          |              :white_check_mark:              |                         |                         |\n| Custom Quality Profiles        |              :white_check_mark:              |                         |                         |\n| Custom Quality Sizes           |              :white_check_mark:              |                         |                         |\n| Custom Naming                  |              :white_check_mark:              |                         |                         |\n| Clear all Custom Formats       |              :white_check_mark:              |                         |                         |\n| Modify Scores                  |              :white_check_mark:              |                         |                         |\n| Profile renaming               |              :white_check_mark:              |                         |                         |\n| Profile cloning                |              :white_check_mark:              |                         |                         |\n\n## Lidarr\n\n| Features                       | <div class=\"configarrHeader\">Configarr</div> | Notifiarr (unsupported) | Recyclarr (unsupported) |\n| ------------------------------ | :------------------------------------------: | :---------------------: | :---------------------: |\n| GUI (graphical user interface) |                                              |                         |                         |\n| Custom Custom Formats          |              :white_check_mark:              |                         |                         |\n| Custom Quality Profiles        |              :white_check_mark:              |                         |                         |\n| Custom Quality Sizes           |              :white_check_mark:              |                         |                         |\n| Custom Naming                  |              :white_check_mark:              |                         |                         |\n| Clear all Custom Formats       |              :white_check_mark:              |                         |                         |\n| Modify Scores                  |              :white_check_mark:              |                         |                         |\n| Profile renaming               |              :white_check_mark:              |                         |                         |\n| Profile cloning                |              :white_check_mark:              |                         |                         |\n"
  },
  {
    "path": "docs/docs/concepts.md",
    "content": "---\nsidebar_position: 2\ndescription: \"Basic concepts and terminology for understanding Sonarr, Radarr, and custom formats\"\nkeywords: [sonarr, radarr, custom formats, quality profiles, media management, automation]\n---\n\n# What are Arr Applications?\n\nThis page explains the fundamental concepts and components that Configarr works with. Understanding these will help you make the most of the tool.\n\n## Media Managers\n\n### Sonarr\n\n[Sonarr](https://sonarr.tv/) is a TV series management tool that automates the downloading and organizing of TV shows. It can monitor multiple RSS feeds for new episodes and will grab, sort, and rename them.\n\n### Radarr\n\n[Radarr](https://radarr.video/) is a movie collection manager that works similarly to Sonarr but focuses on movies instead of TV series. It's designed to automatically download and organize movie files.\n\n## Key Terminology\n\n### Custom Formats\n\nCustom formats are rules that help Sonarr and Radarr identify and prioritize specific characteristics of media files. These can include:\n\n- Video quality (HDR, DV, etc.)\n- Audio formats (DTS, TrueHD, etc.)\n- Release groups\n- Language specifications\n- Encoding settings\n\n### Quality Profiles\n\nQuality profiles define what types of releases you want for your media. They combine:\n\n- Allowed quality types (1080p, 2160p, etc.)\n- Custom format scores\n- Upgrade rules\n- Minimum/maximum size requirements\n\n### Release Groups\n\nRelease groups are teams or individuals who release media content. Different groups often have different standards and specialties for their releases.\n\n### TRaSH-Guides\n\n[TRaSH-Guides](https://trash-guides.info/) is a comprehensive collection of guides and configurations for various media management tools. It provides recommended settings, custom formats, and quality profiles that represent community best practices.\n\n## How They Work Together\n\n- **Quality Profiles** use **Custom Formats** to score and select releases\n- **Sonarr/Radarr** use these profiles to make download decisions\n- **TRaSH-Guides** provides optimized configurations for both\n- **Configarr** helps manage and synchronize all these components\n\n:::tip\nFor detailed setup instructions and tutorials, visit the official documentation for [Sonarr](https://wiki.servarr.com/sonarr) and [Radarr](https://wiki.servarr.com/radarr).\n:::\n"
  },
  {
    "path": "docs/docs/configuration/_category_.json",
    "content": "{\n  \"label\": \"Configuration\",\n  \"position\": 4,\n  \"link\": {\n    \"type\": \"generated-index\"\n  }\n}\n"
  },
  {
    "path": "docs/docs/configuration/_include/config-file-sample.yml",
    "content": "# Optional: Override default repositories\n#trashGuideUrl: https://github.com/BlackDark/fork-TRASH-Guides\n#recyclarrConfigUrl: https://github.com/BlackDark/fork-recyclarr-configs\n\n# Optional: Specify a custom branch/revision (defaults to 'master')\n#trashRevision: master\n#recyclarrRevision: master\n\n# since v1.12.0. Optional. Enable for legacy configuration to clone full repos (trash, recyclarr) instead of sparse.\n# Default is false. Setting to true increases storage usage up to 10x.\n#enableFullGitClone: true\n\n# since v1.22.0. Optional. Enable compatibility mode for TRaSH-Guides changes from Feb 2026.\n# When true: uses old behavior (exclude semantics for CF groups, reversed quality ordering)\n# When false (default): uses new behavior (include semantics, display order for qualities)\n# See: https://github.com/TRaSH-Guides/Guides/commit/2994a7979d8036a7908a92e2cd286054fd4fcc1b\n#compatibilityTrashGuide20260219Enabled: false\n\n# Optional. Silence warnings when a Quality Profile contains CustomFormats marked mutually\n# exclusive in TRaSH-Guides conflicts.json. Sync behavior is unchanged; only logs are suppressed.\n# Default is false.\n#silenceTrashConflictWarnings: true\n\n# Optional: Enable anonymous telemetry tracking of feature usage\n# Default is false. Set to true to help improve Configarr by sharing anonymous usage statistics.\ntelemetry: true\n\n# Optional: Paths for custom formats and templates\nlocalCustomFormatsPath: /app/cfs\nlocalConfigTemplatesPath: /app/templates\n\n# Optional. Custom Format Definitions\ncustomFormatDefinitions:\n  - trash_id: example-in-config-cf\n    trash_scores:\n      default: -10000\n    trash_description: \"Language: German Only\"\n    name: \"Language: Not German\"\n    includeCustomFormatWhenRenaming: false\n    specifications:\n      - name: Not German Language\n        implementation: LanguageSpecification\n        negate: true\n        required: false\n        fields:\n          value: 4\n\n# With this options you can disable or enable processing for *arrs. Default is enabled\n#sonarrEnabled: false\n#radarrEnabled: false\n#whisparrEnabled: false\n#readarrEnabled: false\n#lidarrEnabled: false\n\n# Sonarr Configuration\nsonarr:\n  instance1: # Instance name (can be any unique identifier)\n    base_url: http://sonarr:8989 # Sonarr instance URL\n    api_key: !secret SONARR_API_KEY # Reference to API key in secrets.yml\n    # api_key: !env SONARR_API_KEY # load from environment variable\n    # api_key: !file /run/secrets/sonarr_api_key # Load from a file\n\n    # since v1.11.0. You can disable instances. Optional\n    # enabled: false\n\n    # since v1.12.0. Optional\n    # delete_unmanaged_custom_formats:\n    #   enabled: true\n    #   ignore: # optional\n    #     - some-cf\n\n    # since v1.18.0. Optional\n    # delete_unmanaged_quality_profiles:\n    #   enabled: true\n    #   ignore: # optional\n    #     - some-qp\n\n    # experimental since v1.14.0. Optional\n    # root_folders:\n    #   - /mnt/media/series\n\n    # experimental since v1.14.0. Optional\n    # delay_profiles:\n    #   default:\n    #     enableUsenet: true\n    #     enableTorrent: false\n    #     preferredProtocol: usenet\n    #     usenetDelay: 10\n    #     torrentDelay: 0\n    #     bypassIfHighestQuality: false\n    #     bypassIfAboveCustomFormatScore: false\n    #     minimumCustomFormatScore: 0\n    #   additional:\n    #     - enableUsenet: true\n    #       enableTorrent: false\n    #       preferredProtocol: usenet\n    #       usenetDelay: 10\n    #       torrentDelay: 0\n    #       bypassIfHighestQuality: false\n    #       bypassIfAboveCustomFormatScore: false\n    #       minimumCustomFormatScore: 0\n    #       order: 1\n    #       tags:\n    #         - delayprofiletest\n\n    quality_definition:\n      type: series # Quality definition type for Sonarr\n\n    include: # Template includes\n      - template: sonarr-cf\n      - template: sonarr-quality\n      - template: d1498e7d189fbe6c7110ceaabb7473e6\n        source: TRASH # RECYCLARR (default) or TRASH\n\n      # WEB-1080p (recyclarr template)\n      - template: sonarr-quality-definition-series\n      - template: sonarr-v4-quality-profile-web-1080p\n      - template: sonarr-v4-custom-formats-web-1080p\n\n      # (experimental) since v1.18.0. Load template from URL\n      # - template: https://gist.githubusercontent.com/user/abc123/raw/template.yml\n\n    # experimental available in all *arr\n    #media_management: {}\n\n    # experimental available in all *arr\n    #media_naming_api: {}\n\n    # experimental available in all *arr\n    #ui_config: {}\n\n    # naming from recyclarr: https://recyclarr.dev/wiki/yaml/config-reference/media-naming/\n    #media_naming: {}\n    # (experimental) Manage download clients\n    #download_clients:\n    #  - name: \"Primary qBittorrent\"\n    #    type: qbittorrent\n    #    enable: true\n    #    priority: 1\n    #    remove_completed_downloads: true\n    #    remove_failed_downloads: true\n    #    tags:\n    #      - \"4K\"\n    #      - \"High Quality\"\n    #    fields:\n    #      host: qbittorrent\n    #      port: 8080\n    #      use_ssl: false\n    #      url_base: /\n    #      username: sonarr\n    #      password: changeme\n    #      tv_category: series-4k\n    #\n    #  # Always update password fields even when they match the server\n    #  update_password: false\n    #  # Delete unmanaged download clients\n    #  delete_unmanaged:\n    #    enabled: true\n    #    ignore:\n    #      - \"Manual Test Client\"\n    #  # Global download client configuration\n    #  config:\n    #    download_client_working_folders: \"/downloads,/tmp\"\n    #    enable_completed_download_handling: true\n    #    auto_redownload_failed: false\n    #    auto_redownload_failed_from_interactive_search: false\n    #    # Radarr only: Check interval for finished downloads (in minutes)\n    #    check_for_finished_download_interval: 5\n    #    # Note: auto_redownload_failed_from_interactive_search is not available in Whisparr\n\n    # (experimental) since v1.12.0\n    # allows using the cf-groups from TRaSH-Guide.\n    custom_format_groups:\n      - trash_guide:\n          - id: c4735e1d02e8738044ad4ad1bf58670c # Multiple CFs, default only required=true are loaded\n            #include_unrequired: true # if you want to load all set this to true\n        assign_scores_to:\n          - name: WEB-1080p\n\n    custom_formats: # Custom format assignments\n      - trash_ids:\n          - 47435ece6b99a0b477caf360e79ba0bb # x265 (HD)\n        assign_scores_to:\n          - name: WEB-1080p\n            score: 0\n      - trash_ids:\n          - a3d82cbef5039f8d295478d28a887159 # block HDR10+\n          - 2b239ed870daba8126a53bd5dc8dc1c8 # block DV HDR10+\n        assign_scores_to:\n          - name: WEB-1080p\n            score: -10000\n      - trash_ids:\n          - example-in-config-cf # custom format defined in config.yml\n        assign_scores_to:\n          - name: WEB-1080p\n            score: -5000\n\n    quality_profiles:\n      - name: WEB-1080p\n        upgrade:\n          min_format_score: 10000\n\n    # Ability to rename quality profiles\n    # renameQualityProfiles:\n    # - from: WEB-1080p\n    #   to: RenamedProfile\n\n    # Ability to clone profiles\n    # cloneQualityProfiles:\n    #   - from: RenamedExampleProfile\n    #     to: ClonedProfile\n\n# Radarr Configuration\nradarr:\n  instance1: # Instance name (can be any unique identifier)\n    base_url: http://radarr:7878 # Radarr instance URL\n    api_key: !secret RADARR_API_KEY # Reference to API key in secrets.yml\n\n    quality_definition:\n      type: movie # Quality definition type for Radarr\n\n    include:\n      # Comment out any of the following includes to disable them\n      - template: radarr-quality-definition-movie\n      - template: radarr-quality-profile-hd-bluray-web\n      - template: radarr-custom-formats-hd-bluray-web\n\n    custom_formats: # Custom format assignments\n      - trash_ids:\n          - 9f6cbff8cfe4ebbc1bde14c7b7bec0de # IMAX Enhanced\n        assign_scores_to:\n          - name: HD Bluray + WEB\n            score: 0\n\n    quality_profiles:\n      - name: HD Bluray + WEB\n        # Support added with 1.7.0. Not support in all *arr applications. Check the application if it is supported.\n        language: Any # The name must match available names in *arr\n        # Reset existing CustomFormats scores on profile\n        reset_unmatched_scores:\n          enabled: true\n        upgrade:\n          allowed: true\n          until_quality: Remux-1080p\n          until_score: 10000 # Upgrade until score\n          min_format_score: 100 # The minimum increment to trigger an upgrade\n        min_format_score: 100 # The most important custom format needed to download\n        # Score set from TRaSH-Guide to use\n        #score_set: anime-radarr\n        # Sorting order of qualities in profile. Either top or bottom\n        #quality_sort: top\n        # How to group and sort profiles\n        qualities:\n          - name: Remux-1080p\n            qualities:\n              - Bluray-1080p\n              - Remux-1080p\n          - name: WEB 1080p\n            qualities:\n              - WEBDL-1080p\n              - WEBRip-1080p\n              - HDTV-1080p\n          - name: Bluray-720p\n          - name: WEB 720p\n            qualities:\n              - WEBDL-720p\n              - WEBRip-720p\n              - HDTV-720p\n          - name: Bluray-576p\n          - name: Bluray-480p\n          - name: WEB 480p\n            qualities:\n              - WEBDL-480p\n              - WEBRip-480p\n          - name: DVD\n          - name: SDTV\n\n# experimental support: check https://configarr.rayak.de/docs/configuration/experimental-support\nwhisparr: {}\n\n# experimental support: check https://configarr.rayak.de/docs/configuration/experimental-support\nreadarr: {}\n\n# experimental support: check https://configarr.rayak.de/docs/configuration/experimental-support\nlidarr: {}\n"
  },
  {
    "path": "docs/docs/configuration/_include/unmanaged-customformats.yml",
    "content": "sonarr:\n  instance1:\n    base_url: http://sonarr:8989\n    api_key: !secret SONARR_API_KEY\n\n    # since v1.13.0. You can assign custom formats to quality profiles which are only managed on the server\n    custom_formats:\n      - trash_ids:\n          - 47435ece6b99a0b477caf360e79ba0bb # x265 (HD)\n        assign_scores_to:\n          - name: Any\n\n    # 'Any' already exists on server\n    # quality_profiles:\n    #   - name: Any\n    #     # ...\n"
  },
  {
    "path": "docs/docs/configuration/config-file.md",
    "content": "---\nsidebar_position: 2\ntitle: Configuration File\ndescription: \"Learn how to configure Configarr using config.yml, environment variables, and optional secrets.yml\"\nkeywords: [configarr configuration, yaml config, secrets management, custom formats]\n---\n\nimport CodeBlock from \"@theme/CodeBlock\";\nimport ConfigFileSample from \"!!raw-loader!./\\_include/config-file-sample.yml\";\nimport ExampleUnamanagedCustomFormats from \"!!raw-loader!./\\_include/unmanaged-customformats.yml\";\n\n# Configuration Files\n\nConfigarr uses one required configuration file and one optional file for secrets:\n\n- `config.yml` - Required. Contains your main configuration.\n- `secrets.yml` - Optional. Stores sensitive information when you use `!secret`.\n\n## Usage\n\n1. Create `config.yml`\n2. Decide how you want to provide sensitive values:\n   - Use `!env` and inject environment variables (recommended for containers/Kubernetes)\n   - Or use `!secret` and provide a `secrets.yml` file\n3. Place your configuration files in the Configarr configuration directory\n4. For Docker/Kubernetes, pass secrets using environment variables or mounted secret files\n\nConfigarr will automatically load these configurations on startup and apply them to your Sonarr/Radarr instances.\n\n## Configuration Structure\n\nWe try to be mostly compatible with Recyclarr, but with some small differences.\nWe started this fork since Recyclarr V7 and will not always support newer features.\nAdditionally we implement new features which recyclarr does not support at all like custom formats definitions directly in config or support local folders.\n\n### Templates\n\nConfigarr supports multiple types of templates:\n\n1. **Recyclarr Templates**: Used to define reusable configuration blocks\n   - In Recyclarr also named: `Pre-Built Configuration Files`\n   - Documentation: [Recyclarr Templates Wiki](https://recyclarr.dev/wiki/guide-configs/)\n   - Location: Place template files in the directory specified by `localConfigTemplatesPath`\n\n2. **TRaSH-Guides Templates**: Standard templates from TRaSH-Guides\n   - These are automatically pulled from the TRaSH-Guides repository\n   - `language` field support added with `1.7.0`\n   - Can be overridden using `trashGuideUrl` in config.yml\n   - See [TRaSH-Guides Radarr](https://github.com/TRaSH-Guides/Guides/tree/master/docs/json/radarr/quality-profiles) and [TRaSH-Guides Sonarr](https://github.com/TRaSH-Guides/Guides/tree/master/docs/json/sonarr/quality-profiles) for more information\n   - You must also check all CF-Groups because they will be also included automatically (this is desired by the TRaSH-Guide)\n     - [TRaSH-Guides Radarr CF Groups](https://github.com/TRaSH-Guides/Guides/tree/master/docs/json/radarr/cf-groups)\n     - [TRaSH-Guides Sonarr CF Groups](https://github.com/TRaSH-Guides/Guides/tree/master/docs/json/sonarr/cf-groups)\n     - [Github PR/Explanation](https://github.com/TRaSH-Guides/Guides/pull/2455#discussion_r2297832409)\n     - [Discord Discussion/Explanation](https://discord.com/channels/492590071455940612/1409911784386596894)\n\n3. **URL Templates** <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.18.0</span> <span className=\"badge badge--warning\">Experimental</span>: Load templates directly from HTTP/HTTPS URLs\n   - Allows sharing configurations without copying files or forking repositories\n   - Supports both Recyclarr format (YAML) and TRaSH-Guides format (JSON) templates\n   - Simply provide a URL to a template file in the `include` section\n   - The URL must be accessible via HTTP GET request\n\n**Recyclarr Format (YAML):**\n\n```yaml title=\"config.yml\"\nsonarr:\n  instance1:\n    # ...\n    include:\n      - template: https://gist.githubusercontent.com/user/abc123/raw/template.yml\n      - template: https://example.com/path/to/template.yml\n      - template: local-template-name # Still works with local/recyclarr templates\n```\n\n**TRaSH-Guides Format (JSON):**\n\n```yaml title=\"config.yml\"\nsonarr:\n  instance1:\n    # ...\n    include:\n      - template: https://gist.githubusercontent.com/user/abc123/raw/trash-template.json\n        source: TRASH\n      - template: https://example.com/path/to/trash-profile.json\n        source: TRASH\n```\n\n**Notes:**\n\n- **Experimental**, available since `v1.18.0`\n- URL templates are processed first, before local and Recyclarr templates\n- Failed URL fetches are logged as warnings and skipped gracefully\n- Recyclarr format templates must be valid YAML files following the Recyclarr template format\n- TRaSH-Guides format templates must be valid JSON files following the TRaSH-Guides quality profile format (must include `trash_id` field)\n- When loading TRaSH-Guides templates from URLs, specify `source: TRASH` in the include entry\n- Network requests have a 30-second timeout\n\n### Repository URL Configuration\n\nYou can override the default repository URLs for TRaSH-Guides and Recyclarr templates:\n\n```yaml title=\"config.yml\"\n# Use a custom TRaSH-Guides fork\ntrashGuideUrl: https://github.com/your-org/fork-TRASH-Guides\n\n# Use a custom Recyclarr templates fork\nrecyclarrConfigUrl: https://github.com/your-org/fork-recyclarr-configs\n\n# Optional: Specify a custom branch/revision (defaults to 'master')\ntrashRevision: main\nrecyclarrRevision: develop\n```\n\n**Repository URL Changes:**\n\nWhen you change `trashGuideUrl` or `recyclarrConfigUrl` in your config:\n\n- Configarr automatically detects existing cached repositories\n- If the remote URL has changed, it deletes the old repository and re-clones from the new URL\n- The new repository is cloned fresh, avoiding any divergent branch issues\n- If the revision doesn't exist in the new repository, a clear error message is provided\n- **No manual cache deletion is required** - changes take effect on the next run\n\n**Example Scenario:**\n\n1. Start with default TRaSH-Guides repository\n2. Change `trashGuideUrl` to a custom fork in your config\n3. Run Configarr - it automatically detects the URL change, deletes the old cache, and clones from the new repository\n4. Done! No manual intervention needed.\n\n**Error Handling:**\n\nIf something goes wrong during repository updates:\n\n- Clear error messages are logged explaining what failed\n- The revision may not exist in the new repository\n- The new URL may not be accessible\n- Check the logs for guidance on how to resolve the issue\n\n## Configuration Files Reference\n\nIf you want to deep dive into available values and parameters you can always check the direct source code reference for available configurations: [Source Code](https://github.com/raydak-labs/configarr/blob/main/src/types/config.types.ts)\n\n### config.yml\n\nThe main configuration file that defines your Sonarr and Radarr instances, custom formats, and template includes.\n\n<details>\n  <summary>Config.yml</summary>\n  <CodeBlock language=\"yml\" title=\"config.yml\">{ConfigFileSample}</CodeBlock>\n</details>\n\n### secrets.yml\n\nStore sensitive information like API keys in this file when using `!secret`. Never commit this file to version control.\n\n```yaml title=\"secrets.yml\"\nSONARR_API_KEY: your_sonarr_api_key_here\nRADARR_API_KEY: your_radarr_api_key_here\n```\n\n**Multiple secrets and globs** <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.21.0</span>\n\n`SECRETS_LOCATION` can be a comma-separated list of files and/or glob patterns. All matched files are loaded and merged; later files override earlier ones.\n\n```bash\nSECRETS_LOCATION=./config/secrets.yml,./config/overrides.yml\nSECRETS_LOCATION=./config/secrets/*.yml\n```\n\nIf a single non-glob path is provided and the file doesn't exist, Configarr fails on startup. If a glob has no matches, it continues with empty secrets and logs a warning.\n\n### Value sources (`!secret`, `!env`, `!file`)\n\nConfigarr supports custom YAML tags to load values from different sources:\n\n- `!secret`: Loads a key from `secrets.yml`\n- `!env`: Loads an environment variable\n- `!file`: Loads the contents of a file\n\n```yaml title=\"config.yml\"\nsonarr:\n  series:\n    base_url: !env SONARR_URL\n    api_key: !env SONARR_API_KEY\n\nradarr:\n  default:\n    base_url: !secret RADARR_URL\n    api_key: !secret RADARR_API_KEY\n```\n\nFor Kubernetes, using `!env` is usually the easiest approach because values can come directly from Kubernetes Secrets via container environment variables.\n\n### Enable/Disable\n\nYou can configure enabled `*Arr` instance with options in the `config.yml` file.\nDefault everything is `true`.\n\n```yml\n# true or false\nsonarrEnabled: false\nradarrEnabled: false\nwhisparrEnabled: false\nreadarrEnabled: false\nlidarrEnabled: false\n\n# You can also disable on per instance basis\nsonarr:\n  instance1:\n    # ...\n    enabled: false\n```\n\n### Telemetry <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.16.0</span> {#telemetry}\n\nConfigarr can optionally collect anonymous telemetry data to help improve the application. This includes information about which features are being used, but no personal data, API keys, or configuration details are collected.\n\nTo enable telemetry, add the following to your `config.yml`:\n\n```yaml\ntelemetry: true\n```\n\n**Note:** Telemetry is disabled by default and is completely opt-in. See [Telemetry](./telemetry.md)\n\n### TRaSH CustomFormat conflicts <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.26.0</span> {#trash-cf-conflicts}\n\nConfigarr parses `conflicts.json` from TRaSH-Guides and logs a warning when a Quality Profile\nis configured with two or more CustomFormats that TRaSH marks as mutually exclusive. Sync\nbehavior is **not** changed — the warning is informational only, so you can review whether\nthe combination is intentional.\n\nThe check runs automatically for Radarr and Sonarr instances whenever `conflicts.json` is\navailable in the TRaSH-Guides repository.\n\n#### Silence conflict warnings <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.27.0</span> {#silence-trash-conflict-warnings}\n\nIf you intentionally configure conflicting CustomFormats (or simply want quieter logs), set\nthe top-level flag to skip the check:\n\n```yaml\nsilenceTrashConflictWarnings: true\n```\n\nDefault is `false`. Sync behavior is identical either way — only the warning log is suppressed.\n\n### Sharing download client config with YAML anchors <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.23.0</span> {#yaml-anchors}\n\nYou can avoid repeating the same download client settings by using **YAML anchors** and **merge keys**:\n\n- **Anchors** (`&name`): Define a block once and give it a name.\n- **Aliases** (`*name`): Reuse that block by reference.\n- **Merge key** (`<<: *name`): Merge the anchored block into the current mapping; any keys you add after it override the anchored values.\n\nThis works only when **merge keys are enabled**. Set the environment variable **`CONFIGARR_ENABLE_MERGE=true`** (see [Environment Variables](environment-variables.md)); otherwise `<<` is not interpreted as a merge key and your config may fail to parse or behave unexpectedly.\n\n**Example: shared base with anchors and merge**\n\n```yaml title=\"config.yml (requires CONFIGARR_ENABLE_MERGE=true)\"\nx-download-clients:\n  # Define an anchor for shared instance\n  qb_base: &qb_base\n    name: \"qBit\"\n    type: qbittorrent\n    enable: true\n    priority: 1\n    remove_completed_downloads: true\n    remove_failed_downloads: true\n    # YAML does a shallow merge, thus we must declare another anchor for the nested dictionary if we plan to override values in it\n    fields: &qb_fields\n      host: qbittorrent\n      port: 8080\n      use_ssl: false\n      username: admin\n      password: changeme\n\nsonarr:\n  instance1:\n    base_url: http://sonarr:8989\n    api_key: !secret SONARR_API_KEY\n    download_clients:\n      data:\n        - <<: *qb_base\n          fields:\n            <<: *qb_fields\n            tv_category: series\n          tags: [\"sonarr\"]\n        # Reuse same base, override name and fields\n        - <<: *qb_base\n          name: \"qBit 4K\"\n          fields:\n            <<: *qb_fields\n            tv_category: series-4k\n          tags: [\"4K\"]\n      update_password: false\n      delete_unmanaged:\n        enabled: true\n        ignore: [\"Manual Test Client\"]\n      config:\n        enable_completed_download_handling: true\n        auto_redownload_failed: false\n```\n\nThe anchor (`qb_base: &qb_base`) can live at any level, inside a different key (as above), or at the top level. Each list item merges `*qb_base` with `<<:` and then overrides properties as needed. Without `CONFIGARR_ENABLE_MERGE=true`, use fully inline entries (no `<<`) as in the example at the start of this section.\n\n## Quality Definition / Size\n\nQuality definitions control the min/max/preferred file sizes for each quality level.\nThere are three ways to configure them:\n\n### Method 1: Reference a TRaSH-Guides definition by type (classic)\n\nThe `type` value must match the filename (without `.json`) in the TRaSH-Guides\n[`quality-size`](https://github.com/TRaSH-Guides/Guides/tree/master/docs/json) directory.\n\n**Radarr** — available `type` values:\n\n| `type`          | Description                               |\n| --------------- | ----------------------------------------- |\n| `movie`         | Standard movie sizes                      |\n| `anime`         | Anime movie sizes                         |\n| `sqp-streaming` | Streaming Preferred quality profile sizes |\n| `sqp-uhd`       | UHD Streaming quality profile sizes       |\n\n**Sonarr** — available `type` values:\n\n| `type`   | Description              |\n| -------- | ------------------------ |\n| `series` | Standard TV series sizes |\n| `anime`  | Anime series sizes       |\n\n```yml\nradarr:\n  instance1:\n    quality_definition:\n      type: movie # matches radarr/quality-size/movie.json\n      preferred_ratio: 0.5 # optional: 0.0 (min) to 1.0 (max), default uses TRaSH preferred\n\nsonarr:\n  instance1:\n    quality_definition:\n      type: series # matches sonarr/quality-size/series.json\n```\n\n> **Hint:** `type` (filename-based) may be deprecated in favor of `trash_id` in a future version.\n> See [GitHub Issue #155](https://github.com/raydak-labs/configarr/issues/155).\n\n### Method 2: Include a quality definition via `include` (auto-detected)\n\nWhen you include a TRaSH JSON file with `source: TRASH`, Configarr automatically detects\nwhether it is a quality profile or a quality definition and routes it accordingly.\nThis works for both named trash_id references and URL templates.\n\n**By trash_id:**\n\n```yml\nradarr:\n  instance1:\n    include:\n      - template: aed34b9f60ee115dfa7918b742336277 # movie quality definition\n        source: TRASH\n        preferred_ratio: 0.5 # optional: only applies when include resolves to a QD\n\nsonarr:\n  instance1:\n    include:\n      - template: bef99584217af744e404ed44a33af589 # series quality definition\n        source: TRASH\n```\n\n**By URL:**\n\n```yml\nradarr:\n  instance1:\n    include:\n      - template: https://raw.githubusercontent.com/TRaSH-Guides/Guides/master/docs/json/radarr/quality-size/movie.json\n        source: TRASH\n        preferred_ratio: 0.5\n```\n\n**trash_id reference table:**\n\n| Arr    | `type`          | `trash_id`                         |\n| ------ | --------------- | ---------------------------------- |\n| Radarr | `movie`         | `aed34b9f60ee115dfa7918b742336277` |\n| Radarr | `anime`         | `c2aa9540a57d273a9e03a538efe0ca1b` |\n| Radarr | `sqp-streaming` | `8f1391784833965c476bb6aee95fe328` |\n| Radarr | `sqp-uhd`       | `da8c8c0268b2f304be588132831543d2` |\n| Sonarr | `series`        | `bef99584217af744e404ed44a33af589` |\n| Sonarr | `anime`         | `387e6278d8e06083d813358762e0ac63` |\n\n### Method 3: Manual quality sizes\n\nOverride individual quality sizes directly — useful for fine-tuning without loading\na full TRaSH definition. Can be combined with either method above (manual entries are\napplied last and take precedence for matching quality names).\n\n```yml\nsonarr:\n  instance1:\n    quality_definition:\n      qualities:\n        - quality: \"HDTV-720p\" # must match the name shown in the *arr UI\n          title: AdjustedName # optional: rename in UI\n          min: 17.1\n          preferred: 500\n          max: 1000\n\n# Or in a template file:\nquality_definition:\n  qualities:\n    - quality: \"HDTV-1080p\"\n      min: 20\n      preferred: 500\n      max: 1000\n```\n\n**Notes:**\n\n- `preferred_ratio` (0.0 – 1.0) interpolates between `min` and `max` to compute `preferred`.\n  It applies to `type`-based and `include`-based loading; it has no effect on manual `qualities`.\n- `preferred_ratio` on `include` items applies only to that specific include.\n- Merge order: TRaSH/Recyclarr templates → local templates → config file (`quality_definition.qualities` last).\n- Only supported for Radarr and Sonarr. Not available for Lidarr, Readarr, Whisparr.\n- Available since `v1.9.0`. Include-based QD detection available since `v1.x.0` (TBD).\n\n## Media Naming\n\nYou can use the predefined naming configurations from TRaSH-Guide like in recyclarr with the `media_naming` key.\n\n- [TRaSH-Guide Sonarr Naming](https://github.com/TRaSH-Guides/Guides/blob/master/docs/json/sonarr/naming/sonarr-naming.json)\n- [TRaSH-Guide Radarr Naming](https://github.com/TRaSH-Guides/Guides/blob/master/docs/json/radarr/naming/radarr-naming.json)\n- [Recyclarr Wiki](https://recyclarr.dev/wiki/yaml/config-reference/media-naming/)\n\nThe configuration values differs between Radarr and Sonarr.\n\n**Radarr**\n\n```yml\nradarr:\n  instance1:\n    # Media Naming Configuration\n    media_naming:\n      folder: default\n      movie:\n        rename: true\n        standard: standard\n```\n\n| **Property**     | **Description**                                                               | **Default** |\n| ---------------- | ----------------------------------------------------------------------------- | ----------- |\n| `folder`         | Key for \"Movie Folder Format\". Check debug logs or TRaSH-Guide for values.    | Not synced  |\n| `movie.rename`   | If set to `true`, this enables the \"Rename Movies\" checkbox in the Radarr UI. | Not synced  |\n| `movie.standard` | Key for \"Standard Movie Format\". Check debug logs or TRaSH-Guide for values.  | Not synced  |\n\nAll configurations above directly affect the \"Movie Naming\" settings under **Settings > Media Management** in the Radarr UI. If a property is _not specified_, Configarr will not sync that setting, allowing manual configuration.\n\n---\n\n**Sonarr**\n\n```yml\nsonarr:\n  instance1:\n    # Media Naming Configuration\n    media_naming:\n      series: default\n      season: default\n      episodes:\n        rename: true\n        standard: default\n        daily: default\n        anime: default\n```\n\n| **Property**        | **Description**                                                                 | **Default** |\n| ------------------- | ------------------------------------------------------------------------------- | ----------- |\n| `series`            | Key for \"Series Folder Format\". Check debug logs or TRaSH-Guide for values.     | Not synced  |\n| `season`            | Key for \"Season Folder Format\". Check debug logs or TRaSH-Guide for values.     | Not synced  |\n| `episodes.rename`   | If set to `true`, this enables the \"Rename Episodes\" checkbox in the Sonarr UI. | Not synced  |\n| `episodes.standard` | Key for \"Standard Episode Format\". Check debug logs or TRaSH-Guide for values.  | Not synced  |\n| `episodes.daily`    | Key for \"Daily Episode Format\". Check debug logs or TRaSH-Guide for values.     | Not synced  |\n| `episodes.anime`    | Key for \"Anime Episode Format\". Check debug logs or TRaSH-Guide for values.     | Not synced  |\n\nAll configurations above directly affect the \"Episode Naming\" settings under **Settings > Media Management** in the Sonarr UI. If a property is _not specified_, Configarr will not sync that setting, allowing manual configuration.\n\n## Quality Profile\n\nThis is general structure of a profile which can be directly in the config or in templates.\nSome fields could differ between different \\*arrs if some features between mismatch.\nMostly those should be the same.\n\n```yml\n# ...\n\nsonarr:\n  instance1:\n    # ...\n\n    quality_profiles:\n      - name: ExampleInConfigProfile\n        reset_unmatched_scores:\n          enabled: true # enable to reset scores\n        upgrade:\n          allowed: true # enable to allow updates\n          until_quality: WEB 2160p # update until quality\n          until_score: 1000 # Upgrade until score\n          min_format_score: 5 # Minimum increment for upgrade\n        min_format_score: 0 # Minimum custom format needed to download\n        quality_sort: top # enable to allow updates\n        qualities: # qualtities enabled in the profile. Can be groups.\n          - name: Remux-2160p\n          - name: WEB 2160p # This a group\n            qualities:\n              - WEBDL-2160p\n              - WEBRip-2160p\n          - name: Remux-1080p\n          - name: WEB 1080p\n            qualities:\n              - WEBDL-1080p\n              - WEBRip-1080p\n```\n\n### Quality Profile Rename {#quality-profile-rename}\n\nSupport has been added to allow renaming quality profiles.\nThis is useful if you use existing templates for example from TRaSH-Guides but want to adjust the naming to your liking.\n\nWe achieve the renaming by modifying all names in the quality_profiles fields and custom formats score mappings.\n\n```yml\n# ...\n\nsonarr:\n  instance1:\n    # ...\n\n    # Ability to rename profiles\n    renameQualityProfiles:\n      - from: ExampleProfile # must be the exact name of the existing profile\n        to: RenamedExampleProfile # will be the new profile\n```\n\nNotes:\n\n- not supported in templates and will therefore not be merged!\n- rename order will be displayed in `DEBUG` log like: `DEBUG [16:37:09.377]: Will rename quality profiles in this order: 'ExampleProfile' -> 'RenamedExampleProfile','[German] HD Bluray + WEB' -> 'RenamedProfile'`\n- **experimental**, available since `v1.10.0`\n\n### Quality Profile Cloning {#quality-profile-clone}\n\nSupport has been added to allow cloning quality profiles.\nThis is useful if you use existing templates for example from TRaSH-Guides but want to duplicate and slightly adjust some Custom Format scores or mappings.\n\nWe achieve the clone by duplicating all quality profiles and duplicating all profile references in the custom formats.\n\n:::tip\nThe **ordering** of `rename` and `clone` is important. At the moment we `rename` first and then `clone`!\n:::\n\n```yml\n# ...\n\nsonarr:\n  instance1:\n    # ...\n\n    # Ability to clone profiles\n    cloneQualityProfiles:\n      - from: ExampleProfile # must be the exact name of the existing profile\n        to: ClonedProfile1 # will be the new profile\n```\n\nNotes:\n\n- not supported in templates and will therefore not be merged!\n- clone order will be displayed in `DEBUG` log\n- **experimental**, available since `v1.10.0`\n\n## Custom Formats Definitions {#custom-format-definitions}\n\nCustom formats can be defined in two ways:\n\n1. **Direct in config.yml**: Define custom formats directly in your configuration file\n2. **Separate files**: Store custom formats in separate files in the `localCustomFormatsPath` directory\n3. **Local templates**: Store custom formats in local templates folder which can be included per instance (at the moment only for local templates and not recyclarr git templates)\n\nExample custom format definition:\n\n```yaml title=\"config.yml\"\n# Directly in the main config.yml\ncustomFormatDefinitions:\n  - trash_id: custom-de-only # Unique identifier\n    trash_scores:\n      default: -10000 # Default score for this format\n    trash_description: \"Language: German Only\"\n    name: \"Language: Not German\"\n    includeCustomFormatWhenRenaming: false\n    specifications:\n      - name: Not German Language\n        implementation: LanguageSpecification\n        negate: true\n        required: false\n        fields:\n          value: 4\n# ...\n```\n\n```yaml title=\"local-templates/template1.yml\"\n# or in templates which can be included per instance\ncustomFormatDefinitions:\n  - trash_id: custom-de-only2 # Unique identifier\n    trash_scores:\n      default: -10000 # Default score for this format\n    trash_description: \"Language: German Only 2\"\n    name: \"Language: Not German\"\n    includeCustomFormatWhenRenaming: false\n    specifications:\n      - name: Not German Language\n        implementation: LanguageSpecification\n        negate: true\n        required: false\n        fields:\n          value: 4\n# ...\n```\n\n## Custom Formats\n\n- since `v1.13.0` CustomFormats can now also be assigned to Quality Profiles which only exist on the server and are not configured in the config file or included. This means you can only define the custom format mapping and do not have to provide `quality_profiles` if they already exist on the server.\n\n  <details>\n    <summary>CustomFormats with unmanaged QualityProfiles</summary>\n    <CodeBlock language=\"yml\" title=\"CustomFormats with unmanaged QualityProfiles\">{ExampleUnamanagedCustomFormats}</CodeBlock>\n  </details>\n\n- <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.16.0</span> CustomFormats and CustomFormatGroups do not have to be assigned to a quality profile.\n  If not assigned to a profile they will still be created on the \\*arr instance.\n\n## Cleanup / Deleting CustomFormats {#cleanup-custom-formats}\n\nYou can now enable the option to delete all custom formats which are not managed and used in the quality profiles.\nAdditionally you can provide exceptions which should be ignored from deletions.\n\n```yml\n# ...\n\nsonarr:\n  instance1:\n    # ...\n\n    # (experimental) since v1.12.0. Optional\n    delete_unmanaged_custom_formats:\n      enabled: true\n      ignore: # optional\n        - some-cf\n```\n\nNotes:\n\n- **experimental**, available since `v1.12.0`\n\n## Cleanup / Deleting QualityProfiles {#cleanup-quality-profiles}\n\nYou can now enable the option to delete all quality profiles which are not managed by configarr.\nAdditionally you can provide exceptions on which quality profile(s) should be ignored from deletion.\n\n```yml\n# ...\nsonarr:\n  instance1:\n    # ...\n    # (experimental) since v1.18.0. Optional\n    delete_unmanaged_quality_profiles:\n      enabled: true\n      ignore: # optional\n        - some-qp\n```\n\nNotes:\n\n- **experimental**, available since `v1.18.0`\n\n## CustomFormatGroups {#custom-format-groups}\n\nSupport has been added to allow using the TRaSH-Guide custom format groups: [see here](https://github.com/TRaSH-Guides/Guides/tree/master/docs/json/sonarr/cf-groups).\nThose are logically bundled together CustomFormats which will be applied together.\nTRaSH-Guide is using them in an interactive manner with Notifiarr therefore there are also non required CustomFormats.\nConfigarr will only load required ones (`required: true`).\n\nIf you need some optional ones just add them with the existing `custom_formats` mapping.\nAlso the `quality_profiles` mapping in the JSON file is ignored because it does not make sense in Configarr.\n\n```yml\n# ...\n\nsonarr:\n  instance1:\n    # ...\n\n    # (experimental) since v1.12.0\n    # allows using the cf-groups from TRaSH-Guide.\n    custom_format_groups:\n      - trash_guide:\n          - id: c4735e1d02e8738044ad4ad1bf58670c # Multiple CFs, only where required=true are loaded\n            #include_unrequired: true # if you want to load all set this to true\n        assign_scores_to:\n          - name: MyProfile\n            # (experimental) since v1.16.0\n            #score: 0 # optional score to assign to all custom formats in this group. You can still override custom format scores via custom_formats\n```\n\nNotes:\n\n- **experimental**, available since `v1.12.0`\n\n### TRaSH-Guides Breaking Changes (Feb 2026) {#trash-guides-breaking-changes-2026-02}\n\n:::warning Breaking Change\nTRaSH-Guides made significant changes to their JSON structure in February 2026, affecting both CF group semantics and quality profile ordering.\n:::\n\nFor detailed information about what changed, backward compatibility options, and version matrix, see the [TRaSH-Guides Breaking Changes FAQ](../faq.md#trash-guides-breaking-changes-2026-02).\n\nWith version `v1.22.0` the new format will be processed.\n\n## RootFolders\n\nWith Configarr you can configure the root folders directly.\nJust specify the list of your folders and Configarr will delete and create them as specified in the list.\n\n```yml\n# ...\n\nsonarr:\n  instance1:\n    # ...\n    # (experimental) since v1.14.0\n    root_folders:\n      - /mnt/media/series\n```\n\nNotes:\n\n- **experimental**, available since `v1.14.0`\n\n## Delay Profiles\n\nYou can configure and sync Delay Profiles directly in your config. This allows you to manage the download delay logic for Usenet and Torrent protocols, including protocol preference, delays, and tag-based rules.\n\nA delay profile without any tags is considered the `default` profile. Any other profiles with tags are `additional` profiles.\n\n```yaml\nyourarr:\n  instance1:\n    # (experimental) since v1.14.0\n    delay_profiles:\n      default:\n        enableUsenet: true\n        enableTorrent: false\n        preferredProtocol: usenet\n        usenetDelay: 10\n        torrentDelay: 0\n        bypassIfHighestQuality: false\n        bypassIfAboveCustomFormatScore: false\n        minimumCustomFormatScore: 0\n      additional:\n        - enableUsenet: false\n          enableTorrent: true\n          preferredProtocol: torrent\n          usenetDelay: 0\n          torrentDelay: 20\n          bypassIfHighestQuality: true\n          bypassIfAboveCustomFormatScore: false\n          minimumCustomFormatScore: 0\n          order: 2\n          tags:\n            - mytag\n```\n\nNotes:\n\n- **experimental**, available since `v1.14.0`\n- Supported for Sonarr, Radarr, Whisparr, Lidarr, and Readarr (if the API supports it)\n- If a delay profile exists on the server but not in your config, it will be deleted\n- If a delay profile is in your config but not on the server, it will be created (if supported)\n- If a delay profile differs, it will be updated\n\nSee example [Radarr API DelayProfile](https://radarr.video/docs/api/#/DelayProfile) for more details on available fields.\n\n## Download Clients <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.19.0</span>\n\nConfigarr can (experimentally) manage the **Download Clients** configured in your \\*Arr\napplications (for example qBittorrent, Transmission, SABnzbd, etc.).\n\n- Create download clients that exist in the config but not in the \\*Arr instance\n- Optionally delete unmanaged download clients\n- Manage global download client configuration settings since `v1.19.0`\n\n```yaml title=\"config.yml (inline, no top-level)\"\nsonarr:\n  instance1:\n    base_url: http://sonarr:8989\n    api_key: !secret SONARR_API_KEY\n\n    # (experimental) Manage Sonarr download clients\n    download_clients:\n      data:\n        - name: \"qBit 4K\"\n          type: qbittorrent\n          enable: true\n          priority: 1\n          remove_completed_downloads: true\n          remove_failed_downloads: true\n          tags:\n            - \"4K\" # Tag name (auto-resolved to ID)\n          fields:\n            host: qbittorrent\n            port: 8080\n            use_ssl: false\n            url_base: /\n            username: sonarr\n            password: changeme\n            tv_category: series-4k\n       # Set to true to always update password otherwise existing passwords will not be updated because we can not retrieve existing passwords\n       update_password: false\n      # Delete unmanaged download clients\n      delete_unmanaged:\n        enabled: true\n        ignore:\n          - \"Manual Test Client\"\n      # (since v1.19.0) Global download client configuration\n      config:\n        enable_completed_download_handling: true\n        auto_redownload_failed: false\n        auto_redownload_failed_from_interactive_search: false\n        # Radarr only: Check interval for finished downloads (in minutes)\n        # check_for_finished_download_interval: 1\n```\n\n### Download Client Configuration <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.19.0</span>\n\nThe `config` section allows you to manage global download client settings.\nPlease check the \\*Arr-specific API for supported fields (in the REST APIs under `DownloadClientConfig`).\n\n- convert the camelCase typed fields like `enableCompletedDownloadHandling` to snake_case: `enable_completed_download_handling`\n\n**Note:** Instance-specific fields (like `check_for_finished_download_interval` for Radarr) are automatically filtered based on the \\*Arr type. Unsupported fields are safely ignored.\n\n### Remote Path Mappings <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.20.0</span>\n\nRemote path mappings allow you to remap remote paths from your download clients to local paths that your \\*Arr applications can access. This is useful when your download client is running on a different machine or in a container with different mount paths.\n\n```yaml title=\"config.yml\"\nradarr:\n  instance1:\n    download_clients:\n      remote_paths:\n        - host: \"transmission\"\n          remote_path: \"/downloads/complete\"\n          local_path: \"/data/media/downloads/complete\"\n        - host: \"192.168.1.100\"\n          remote_path: \"/torrents\"\n          local_path: \"/mnt/torrents\"\n```\n\n**Key Points:**\n\n- `host`: The hostname or IP address of the download client\n- `remote_path`: The path as seen by the download client (must be unique combined with host)\n- `local_path`: The path as accessible by the \\*Arr application\n- Mappings are matched by `host + remote_path` combination (must be unique)\n- Existing mappings with matching host+remote_path will be updated with new local_path\n- Mappings that exist on the server but not in config will be deleted\n\nFor more details, check the \\*Arr-specific API documentation under the `RemotePathMapping` resource endpoint.\n\n## Experimental supported fields\n\n- Experimental support for `media_management` and `media_naming_api` (since v1.5.0)\n  With those you can configure different settings in the different tabs available per *arr.\n  These fields are under experimental support.\n  The supported elements are dependent on the *arr used.\n  Check the following API documentation of available fields:\n\n  Naming APIs:\n  - https://radarr.video/docs/api/#/NamingConfig/get_api_v3_config_naming\n  - https://sonarr.tv/docs/api/#/NamingConfig/get_api_v3_config_naming\n  - https://whisparr.com/docs/api/#/NamingConfig/get_api_v3_config_naming\n  - https://readarr.com/docs/api/#/NamingConfig/get_api_v1_config_naming\n  - https://lidarr.audio/docs/api/#/NamingConfig/get_api_v1_config_naming\n\n  MediaManagement APIs:\n  - https://radarr.video/docs/api/#/MediaManagementConfig/get_api_v3_config_mediamanagement\n  - https://sonarr.tv/docs/api/#/MediaManagementConfig/get_api_v3_config_mediamanagement\n  - https://whisparr.com/docs/api/#/MediaManagementConfig/get_api_v3_config_mediamanagement\n  - https://readarr.com/docs/api/#/MediaManagementConfig/get_api_v1_config_mediamanagement\n  - https://lidarr.audio/docs/api/#/MediaManagementConfig/get_api_v1_config_mediamanagement\n\n- Experimental support for `ui_config` (since v1.21.0)\n  Allows configuring UI settings like theme, language, calendar preferences, etc.\n  The supported elements are dependent on the \\*arr used.\n  Check the following API documentation of available fields:\n\n  UI Config APIs:\n  - https://radarr.video/docs/api/#/UiConfig/put_api_v3_config_ui__id_\n  - https://sonarr.tv/docs/api/#v3/tag/uiconfig/PUT/api/v3/config/ui/{id}\n  - https://whisparr.com/docs/api/#/UiConfig/put_api_v3_config_ui__id_\n  - https://readarr.com/docs/api/#/UiConfig/put_api_v1_config_ui__id_\n  - https://lidarr.audio/docs/api/#/UiConfig/put_api_v1_config_ui__id_\n"
  },
  {
    "path": "docs/docs/configuration/environment-variables.md",
    "content": "---\nsidebar_position: 3\ntitle: Environment Variables\ndescription: \"Learn about the environment variables used in our application configuration.\"\nkeywords: [environment variables, configuration, setup]\n---\n\n# Environment Variables\n\nThis document outlines the available environment variables for configuring Configarr besides the config files.\nEach variable can be set to customize the behavior of the application.\n\n## Available Environment Variables\n\n| Variable Name                         | Default Value             | Required | Description                                                                                                                                                                                                                                                                   |\n| ------------------------------------- | ------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `LOG_LEVEL`                           | `\"info\"`                  | No       | Sets the logging level. Options are `trace`, `debug`, `info`, `warn`, `error`, and `fatal`.                                                                                                                                                                                   |\n| `LOG_STACKTRACE`                      | `\"false\"`                 | No       | (Experimental, v1.11.0) Outputs additionally stacktraces of underlying errors.                                                                                                                                                                                                |\n| `CONFIG_LOCATION`                     | `\"./config/config.yml\"`   | No       | Specifies the path to the configuration file.                                                                                                                                                                                                                                 |\n| `SECRETS_LOCATION`                    | `\"./config/secrets.yml\"`  | No       | Specifies the path to the secrets file. Since `v1.21.0`, can be a comma-separated list and/or glob patterns; later files override earlier ones.                                                                                                                               |\n| `CUSTOM_REPO_ROOT`                    | `\"./repos\"`               | No       | Defines the root directory for custom repositories.                                                                                                                                                                                                                           |\n| `ROOT_PATH`                           | Current working directory | No       | Sets the root path for the application. Defaults to the current working directory.                                                                                                                                                                                            |\n| `DRY_RUN`                             | `\"false\"`                 | No       | When set to `\"true\"`, runs the application in dry run mode without making changes.                                                                                                                                                                                            |\n| `LOAD_LOCAL_SAMPLES`                  | `\"false\"`                 | No       | If `\"true\"`, loads local sample data for testing purposes.                                                                                                                                                                                                                    |\n| `DEBUG_CREATE_FILES`                  | `\"false\"`                 | No       | Enables debugging for file creation processes when set to `\"true\"`.                                                                                                                                                                                                           |\n| `TZ`                                  | `\"Etc/UTC\"`               | No       | Timezone for the container.                                                                                                                                                                                                                                                   |\n| `STOP_ON_ERROR`                       | `\"false\"`                 | No       | (Experimental, v1.11.0) Stop execution on any error on any instance.                                                                                                                                                                                                          |\n| `TELEMETRY_ENABLED`                   | `undefined`               | No       | Enables anonymous telemetry tracking of feature usage. Takes precedence over config file setting.                                                                                                                                                                             |\n| `CONFIGARR_DISABLE_GIT_CLONE_OPTIONS` | `undefined`               | No       | Disables custom git clone options (`--filter=blob:none` and `--sparse`). Use this if you encounter errors like \"Function not implemented\" or \"index-pack failed\" with legacy kernels. See [troubleshooting](../faq.md#git-clone-errors-with-legacy-kernels) for more details. |\n| `CONFIGARR_ENABLE_MERGE`              | `\"false\"`                 | No       | When set to `\"true\"`, enables YAML merge keys (`<<`) when parsing the main config file, so you can use anchors and merge keys to share and override config blocks. See [config file](config-file.md) for details.                                                             |\n\n## Usage\n\nTo use these environment variables, set them in your shell or include them in your deployment configuration via docker or kubernetes.\n\n## Examples\n\n- For example you change the default path for all configs, repos with the `ROOT_PATH` variables.\n  As default it would store them inside the application directory (in the container this is `/app`)\n\n## References\n\nCheck the `.env.template` file in the repository [Github](https://github.com/raydak-labs/configarr/blob/main/.env.template)\n"
  },
  {
    "path": "docs/docs/configuration/experimental-support.md",
    "content": "---\nsidebar_position: 4\ntitle: Experimental Support\ndescription: \"Experimental and testing support for other *Arr tools\"\nkeywords: [configarr configuration, yaml config, custom formats, expermintal, whisparr, readarr, lidarr]\n---\n\n# Experimental support\n\nThis section describes experimental support for other \\*Arr tools.\nThis means that some features of configarr are working as expected but not every feature must be supported.\n\n:::warning\nThis is experimental and testing support. Support could be dropped in the future.\n:::\n\n## Whisparr v3\n\nExperimental support for Whisparr was added with [v1.4.0](https://github.com/raydak-labs/configarr/releases/tag/v1.4.0).\n\nConfiguration is mostly equal to the Sonarr or Radarr.\n\nFollowing things are currently not supported or tested:\n\n- quality definition preset is not evaluated\n  ```yaml\n  quality_definition:\n    type: movie # not checked yet\n  ```\n- initial language of quality profiles is not correct -> `0`\n- no available presets because nothings provided in TRaSH-Guides or recyclarr -> needs to be done manually with local templates and custom formats\n\n### Configuration File\n\nCheck [configuration file reference](/docs/configuration/config-file#custom-format-definitions) for more information.\n\n```yaml title=\"config.yml\"\nlocalCustomFormatsPath: /app/cfs\nlocalConfigTemplatesPath: /app/templates\n\ncustomFormatDefinitions:\n  - trash_id: example-in-config-cf\n    trash_scores:\n      default: -10000\n    trash_description: \"Language: German Only\"\n    name: \"Language: Not German\"\n    includeCustomFormatWhenRenaming: false\n    specifications:\n      - name: Not German Language\n        implementation: LanguageSpecification\n        negate: true\n        required: false\n        fields:\n          value: 4\n\n# experimental support: check https://configarr.rayak.de/docs/configuration/experimental-support\nwhisparr:\n  instance1: # Instance name (can be any unique identifier)\n    base_url: http://whisparr:6969 # instance URL\n    api_key: !secret WHISPARR_API_KEY # Reference to API key in secrets.yml\n\n    quality_definition:\n      type: movie # TODO: not checked yet\n\n    include:\n      # only custom defined templates available\n      - template: whisparr\n\n    custom_formats: # Custom format assignments\n      - trash_ids:\n          - example-in-config-cf\n        assign_scores_to:\n          - name: ExampleProfile\n            score: 1000\n\n    quality_profiles:\n      # TODO: language not correctly mapped\n      - name: ExampleProfile\n        upgrade:\n          until_score: 200\n          # Not supported in whisparr\n          #min_format_score: 200\n```\n\n## Readarr v1\n\nExperimental support for Readarr was added with [v1.4.0](https://github.com/raydak-labs/configarr/releases/tag/v1.4.0).\n\nMetadata profiles support was added with [v1.19.0](https://github.com/raydak-labs/configarr/releases/tag/v1.19.0).\n\nConfiguration is mostly equal to the Sonarr or Radarr.\n\nFollowing things are currently not supported or tested:\n\n- quality definition preset is not evaluated\n  ```yaml\n  quality_definition:\n    type: movie # not checked yet\n  ```\n- no available presets because nothings provided in TRaSH-Guides or recyclarr -> needs to be done manually with local templates and custom formats\n\n### Configuration File\n\nCheck [configuration file reference](/docs/configuration/config-file#custom-format-definitions) for more information.\n\n```yaml title=\"config.yml\"\nlocalCustomFormatsPath: /app/cfs\nlocalConfigTemplatesPath: /app/templates\n\ncustomFormatDefinitions:\n  - trash_id: example-release-title-cf\n    trash_scores:\n      default: 0\n    name: ExampleReleaseTitleCF\n    includeCustomFormatWhenRenaming: false\n    specifications:\n      - name: Preferred Words\n        implementation: ReleaseTitleSpecification\n        negate: false\n        required: false\n        fields:\n          value: \"\\\\b(SPARKS|Framestor)\\\\b\"\n\n# experimental support: check https://configarr.rayak.de/docs/configuration/experimental-support\nreadarr:\n  instance1: # Instance name (can be any unique identifier)\n    base_url: http://readarr:8787 # instance URL\n    api_key: !secret READARR_API_KEY # Reference to API key in secrets.yml\n\n    # not supported\n    # quality_definition:\n    #   type: movie # Quality definition type\n\n    include:\n      # only custom defined templates available\n      - template: readarr\n\n    custom_formats: # Custom format assignments\n      - trash_ids:\n          - example-release-title-cf\n        assign_scores_to:\n          - name: ExampleProfile\n            score: 1000\n\n    quality_profiles:\n      - name: ExampleProfile\n        upgrade:\n          until_score: 200\n\n    # Metadata Profiles (since v1.19.0)\n    metadata_profiles:\n      - name: Standard\n        min_popularity: 10\n        skip_missing_date: true\n        skip_missing_isbn: false\n        skip_parts_and_sets: false\n        skip_secondary_series: false\n        allowed_languages:\n          - eng # ISO 639-3 language codes\n          - deu\n          - null # Allow books with no language\n        min_pages: 50\n        must_not_contain:\n          - \"Abridged\"\n          - \"Large Print\"\n\n    # Delete unmanaged metadata profiles (since v1.19.0)\n    delete_unmanaged_metadata_profiles:\n      enabled: true\n      ignore:\n        - Default\n```\n\n:::tip Language Codes\nReadarr metadata profiles use **ISO 639-3** language codes (3-letter codes like `eng`, `deu`, `fra`).\n:::\n\n## Lidarr v2\n\nExperimental support for Lidarr was added with [v1.8.0](https://github.com/raydak-labs/configarr/releases/tag/v1.8.0).\n\nConfiguration is mostly equal to the Sonarr or Radarr.\n\nFollowing things are currently not supported or tested:\n\n- quality definition preset is not evaluated\n  ```yaml\n  quality_definition:\n    type: movie # not checked yet\n  ```\n- no available presets because nothings provided in TRaSH-Guides or recyclarr -> needs to be done manually with local templates and custom formats\n\n### Configuration File\n\nCheck [configuration file reference](/docs/configuration/config-file#custom-format-definitions) for more information.\n\n```yaml title=\"config.yml\"\nlocalCustomFormatsPath: /app/cfs\nlocalConfigTemplatesPath: /app/templates\n\ncustomFormatDefinitions:\n  - trash_id: example-release-title-cf\n    trash_scores:\n      default: 0\n    name: ExampleReleaseTitleCF\n    includeCustomFormatWhenRenaming: false\n    specifications:\n      - name: Preferred Words\n        implementation: ReleaseTitleSpecification\n        negate: false\n        required: false\n        fields:\n          value: \"\\\\b(SPARKS|Framestor)\\\\b\"\n\n# experimental support: check https://configarr.rayak.de/docs/configuration/experimental-support\nlidarr:\n  instance1:\n    # Set the URL/API Key to your actual instance\n    base_url: http://lidarr:8686\n    api_key: !secret LIDARR_API_KEY\n\n    # not supported\n    # quality_definition:\n    #   type: movie # Quality definition type\n\n    include:\n      # only custom defined templates available\n      - template: lidarr\n\n    custom_formats: # Custom format assignments\n      - trash_ids:\n          - example-release-title-cf\n        assign_scores_to:\n          - name: ExampleProfile\n            score: 1000\n\n    quality_profiles:\n      # TODO: language not correctly mapped\n      - name: ExampleProfile\n        upgrade:\n          until_score: 200\n          # Not supported\n          #min_format_score: 200\n\n    # Metadata Profiles (since v1.19.0)\n    metadata_profiles:\n      - name: Standard\n        # at least one required\n        primary_types:\n          - Album\n          - EP\n          - Single\n        # at least one required\n        secondary_types:\n          - Studio\n          - Compilation\n          - Soundtrack\n        # at least one required\n        release_statuses:\n          - Official\n\n    # Delete unmanaged metadata profiles (since v1.19.0)\n    delete_unmanaged_metadata_profiles:\n      enabled: true\n      ignore:\n        - SomeProfile\n```\n\n### Lidarr Metadata Profile Fields\n\n| Field              | Type         | Description                                                                           |\n| ------------------ | ------------ | ------------------------------------------------------------------------------------- |\n| `name`             | string       | **Required.** Profile name (must be unique)                                           |\n| `primary_types`    | string array | **Required.** List of enabled primary album types (Album, EP, Single, Broadcast)      |\n| `secondary_types`  | string array | **Required.** List of enabled secondary album types (Studio, Live, Compilation, etc.) |\n| `release_statuses` | string array | **Required.** List of enabled release statuses (Official, Promotion, Bootleg, etc.)   |\n\n**How it works:**\n\n- Only types/statuses listed in the arrays will be **enabled** (allowed)\n- All other types/statuses will be **disabled**\n\n**Example:**\n\n```yaml\nmetadata_profiles:\n  - name: Standard\n    primary_types: [Album, EP] # Only Album and EP enabled\n    secondary_types: [Studio, Compilation] # Only Studio and Compilation enabled\n    release_statuses: [Official] # Only Official enabled\n```\n\n## Metadata Profiles - Common Configuration\n\nBoth Readarr and Lidarr support metadata profiles to control what content is accepted.\n\n### Delete Unmanaged Profiles\n\nYou can automatically delete metadata profiles that exist on the server but are not defined in your configuration.\n\n**Simple form:**\n\n```yaml\ndelete_unmanaged_metadata_profiles: true\n```\n\n**Full form with ignore list:**\n\n```yaml\ndelete_unmanaged_metadata_profiles:\n  enabled: true\n  ignore:\n    - LegacyProfile\n    - CustomProfile\n```\n\n:::warning Built-in Protection\nThe `None` profile (Lidarr/Readarr) is **always** protected from deletion, even if not in the ignore list.\n:::\n\n### Templates and Metadata Profiles\n\nMetadata profiles can be defined in templates and merged with instance configurations.\n\n**Template:**\n\n```yaml title=\"templates/readarr.yml\"\nmetadata_profiles:\n  - name: Standard\n    min_popularity: 10\n\ndelete_unmanaged_metadata_profiles:\n  enabled: true\n  ignore: []\n```\n\n**Instance:**\n\n```yaml title=\"config.yml\"\nreadarr:\n  instance1:\n    include:\n      - template: readarr\n    metadata_profiles:\n      - name: Audiobooks\n        min_popularity: 5\n```\n\n**Result:** Instance has both \"Standard\" (from template) and \"Audiobooks\" (from instance).\n\n### Best Practices\n\n1. **Test with dry run first:**\n\n   ```bash\n   DRY_RUN=true npm start\n   ```\n\n2. **Use ISO 639-3 language codes** for Readarr (e.g., `eng`, `deu`, not `en`, `de`)\n\n3. **Be explicit about deletion** - always specify ignore list:\n\n   ```yaml\n   delete_unmanaged_metadata_profiles:\n     enabled: true\n     ignore:\n       - Legacy\n   ```\n\n4. **Use templates** for shared profiles across instances\n"
  },
  {
    "path": "docs/docs/configuration/general.md",
    "content": "---\nsidebar_position: 1\ntitle: Basics\ndescription: \"Here we describe how configarr generally works and how things are done.\"\nkeywords: [general, concept, merge, order, basic]\n---\n\n# Basics\n\nHere we try to explain how things are handled and done in configarr so you understand how and when things happen.\n\n## Merge strategies and orderings\n\nBecause we are working with multiple files, orders, includes and more merging needs to be handled.\nWhat kind of data sets do we have?\n\n- TRaSH Repository (CustomFormats, QualityProfiles, QualityDefinition)\n- Recyclarr Templates (QualityProfiles)\n- Local Files (CustomFormats, Templates)\n- Templates (CustomFormats, QualityProfiles)\n- Config file (CustomFormats, QualityProfiles)\n\nThe general concept is: more precise or better closer to the main `config` the later it will be merged and takes precendence.\n\nAt the moment we have the following order:\n\n- TRaSH\n- Recyclarr templates\n- Local Files\n- Config file (global level)\n- Config file (instance level)\n\nAnd this applies for all kind of things: CustomFormats how they are loaded and probably overwritten, QualityProfiles, CustomFormat Mappings to QualityProfiles.\nIf we find some duplicates we will print a log message that something is overwritten or will be ignored.\nIf you find somethting which does not work as expected please create an issue so we can investigate and fix it.\n\n## Folder structure {#folder-structure}\n\nConfigarr uses following folders for storing configurations, cache or data.\nSome of those can be configured via configuration others via environment variables.\n\n| Folder      | Default in container | Required | Description                                                                                                                                                                                                                                                                      |\n| ----------- | -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `cfs`       | `unset`              | No       | Optional. Defines location for own custom formats in JSON format (like TRaSH-Guide uses it). Those are directly available your configuration.                                                                                                                                    |\n| `templates` | `unset`              | No       | Optional. Location for your own templates to be included.                                                                                                                                                                                                                        |\n| `config`    | `/app/config`        | Yes      | Specifies the path to the configuration folder containing the `config.yml` and `secrets.yml` file.                                                                                                                                                                               |\n| `repos`     | `/app/repos`         | Yes      | Location for the repos which are cloned and cached (like TRaSH-Guide, Recyclarr configs). Added with `1.13.3`: Permission independent git ownership (before you had to make sure git folder matches running user in container to not get an git error like \"dubious ownership\"). |\n"
  },
  {
    "path": "docs/docs/configuration/scheduled.md",
    "content": "---\nsidebar_position: 5\ntitle: Scheduling\ndescription: \"How to run configarr regulary/schedueld\"\nkeywords: [configarr configuration, schedule, scheduler, regular, cron]\n---\n\n# Scheduling configarr\n\nThis section describes how you could run configarr in a scheduled manner.\nDepending on your used deployment or third party ([check](../installation/third-party.md)) you can have different methods for running in schedule.\n\n:::info\nConfigarr does not support scheduled execution inside the container and most likely never will.\nScheduling functionalities should be something which is handled outside of the container and something which should be bundled inside each app.\n:::\n\n## Kubernetes\n\nKubernetes support for scheduling jobs is straigthforward.\nWe have explicit resources for this tasks: `CronJobs`\nSee [Kubernetes Setup](/docs/installation/kubernetes) for more information.\n\n## Docker\n\nFor docker and docker-compose we do not have explicit functionalities.\nTherefore we have to create our own scheduled tasks.\nThere are different ways we could achieve this:\n\n- default cron scheduler on linux systems\n- running scheduler containers which executes other docker containers\n\nWe have create examples for how to run the container based solutions.\nCheck [examples/scheduled](/docs/examples#scheduled-example) for more information.\n"
  },
  {
    "path": "docs/docs/configuration/telemetry.md",
    "content": "---\nsidebar_position: 5\ntitle: Telemetry\ndescription: \"Learn about Configarr's anonymous telemetry and how it helps improve the project.\"\nkeywords: [telemetry, analytics, privacy, opt-in, feature usage]\n---\n\nimport Admonition from \"@theme/Admonition\";\n\n# Telemetry <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.16.0</span>\n\nConfigarr includes an **optional, anonymous telemetry system** that helps us understand how the application is being used. This data allows us to make better decisions about feature development, bug fixes, and overall project direction.\n\n<Admonition type=\"info\" title=\"Privacy First\">\n  **Telemetry is completely disabled by default.** You must explicitly opt-in to participate. No data is collected unless you enable it.\n</Admonition>\n\n## What Data Is Collected?\n\n<Admonition type=\"info\" title=\"Help Improve Configarr\">\n  If you're enjoying Configarr and want to help make it even better, please consider enabling telemetry. Your participation helps ensure the project continues to evolve in ways that benefit the community! 🙏\n</Admonition>\n\nWhen enabled, Configarr collects **anonymous, aggregated usage statistics** that include:\n\n### Feature Usage\n\n- Which Arr applications are being configured (Sonarr, Radarr, etc.)\n- Which features are actively used:\n  - Custom format groups and scoring\n  - Quality definitions and profiles\n  - Media management and naming settings\n  - Delay profiles and root folder management\n  - Template sources (Recyclarr vs TRaSH-Guide vs local)\n\n### Instance Statistics\n\n- Number of configured instances per Arr type\n- Whether instances are enabled or disabled\n- Template usage counts by source\n\n### Technical Information\n\n- Configarr version number\n- No personal information, API keys, or configuration details\n\n<Admonition type=\"warning\" title=\"What We DON'T Collect\">\n  - **No personal data**: usernames, emails, IP addresses, or any identifying information\n  - **No configuration details**: API keys, server URLs, custom format names, or file paths\n  - **No sensitive data**: passwords, secrets, or any configuration values\n  - **No usage patterns**: when you run Configarr or how often\n</Admonition>\n\n## Why Telemetry?\n\nTelemetry helps us:\n\n1. **Prioritize development** - Understand which features are most valuable to users\n2. **Identify issues** - Detect problems with specific configurations or features\n3. **Guide decisions** - Make informed choices about new features and improvements\n4. **Improve stability** - Focus testing and bug fixes on commonly used features\n\n## How to Enable Telemetry\n\n### Option 1: Config File (Recommended)\n\nAdd the following to your `config.yml`:\n\n```yaml\n# Enable anonymous telemetry to help improve Configarr\ntelemetry: true\n```\n\n### Option 2: Environment Variable\n\nSet the environment variable:\n\n```bash\nexport TELEMETRY_ENABLED=true\n```\n\n<Admonition type=\"tip\" title=\"Precedence\">\n  The environment variable `TELEMETRY_ENABLED` takes precedence over the config file setting if both are specified.\n</Admonition>\n\n## How It Works\n\n- Data is sent **anonymously** to our analytics service\n- Collection happens **once per run** after configuration parsing\n- Uses industry-standard privacy practices\n- When enabled, you'll see a log message: `\"Telemetry enabled - Thank you for helping improve Configarr!\"`\n- You can disable it at any time by removing the setting\n\n## Example Configuration\n\n```yaml title=\"config.yml\"\n# Your normal configuration\nsonarr:\n  instance1:\n    base_url: http://sonarr:8989\n    api_key: !secret SONARR_API_KEY\n\n# Enable telemetry (optional)\ntelemetry: true\n```\n\n## Opting Out\n\nTelemetry is **disabled by default**. If you have it enabled and want to disable it:\n\n- Remove `telemetry: true` from your config file, or\n- Set `telemetry: false`, or\n- Remove/unset the `TELEMETRY_ENABLED` environment variable\n\n## Questions?\n\nIf you have questions about telemetry or privacy:\n\n- Check our [FAQ](../faq.md) for common questions\n- Review the [source code](https://github.com/raydak-labs/configarr/tree/main/src/telemetry.ts) to see exactly what data is collected\n- Open an issue on [GitHub](https://github.com/raydak-labs/configarr/issues) if you have concerns\n\n---\n"
  },
  {
    "path": "docs/docs/examples.md",
    "content": "---\nsidebar_position: 6\ndescription: \"Examples of Configarr usage and configuration\"\nkeywords: [configarr, examples, configuration, sonarr, radarr]\n---\n\n# HowTo / Examples\n\n## How To's\n\n### Implementation of TRaSH-Guide Profiles\n\n**Q: I want to implement the German Radarr Profile**\n\nLet's say we want to implement the German Profile for Radarr.\nVisit the [TRaSH-Guide page](https://trash-guides.info/Radarr/radarr-setup-quality-profiles-german-en/) and read through the requirements.\nSome parts have to be done via UI like configuring naming, repacks/proper etc.\nOnce those parts are done, we can start with the Custom Formats and QualityProfiles.\n\nFor this approach, we can do 3 different things:\n\n<details>\n  <summary>Use existing TRaSH-Guide profile</summary>\n\nTRaSH-Guide provides predefined profiles via JSON for [Radarr](https://github.com/TRaSH-Guides/Guides/tree/master/docs/json/radarr/quality-profiles) and [Sonarr](https://github.com/TRaSH-Guides/Guides/tree/master/docs/json/sonarr/quality-profiles) in the Github Repository.\nTo load QualityProfiles from TRaSH-Guide, use the `trash_id` defined in the profile and specify `source` as `TRASH` in the config.\n\nIn this example, we want `german-hd-bluray-web.json`\n\n```yml title=\"config.yml\"\n# ...\nradarr:\n  instance1:\n    # ...\n\n    include:\n      - template: 2b90e905c99490edc7c7a5787443748b\n        source: TRASH\n```\n\nAnd that's it.\nNow you can adjust custom formats if needed.\n\n```yml title=\"config.yml\"\n# ...\nradarr:\n  instance1:\n    # ...\n\n    custom_formats:\n      - trash_ids:\n          - 3bc8df3a71baaac60a31ef696ea72d36\n        assign_scores_to:\n          - name: \"[German] HD Bluray + WEB\"\n            score: 400\n```\n\n</details>\n\n<details>\n  <summary>Use existing Recyclarr templates</summary>\n\nYou can use existing Recyclarr templates if available.\nCheck the [Recyclarr Wiki](https://recyclarr.dev/wiki/guide-configs/) or [Github Repository](https://github.com/recyclarr/config-templates/tree/master/radarr).\n\nTwo possibility here:\n\n1. Copy & paste the provided template from the wiki\n2. use only the templates (if templates for everything are provided. Must be in the includes dir.)\n\n(Hint: the value in the template field is the file name of the Recyclarr template without the extension)\n\n1. For this example, we try to implement `German HD Bluray + WEB`.\n\n```yml title=\"copy&paste\"\n# ...existing code...\nradarr:\n  hd-bluray-web-ger:\n    # ...\n    include:\n      - template: radarr-quality-definition-movie\n      - template: radarr-custom-formats-hd-bluray-web-german\n      - template: radarr-quality-profile-hd-bluray-web-german\n\n    quality_profiles:\n      - name: HD Bluray + WEB (GER)\n        # min_format_score: 10000 # Uncomment this line to skip English Releases\n\n    custom_formats:\n      ### Optional\n      - trash_ids:\n        #  - b6832f586342ef70d9c128d40c07b872 # Bad Dual Groups\n        #  - 90cedc1fea7ea5d11298bebd3d1d3223 # EVO (no WEBDL)\n        #  - ae9b7c9ebde1f3bd336a8cbd1ec4c5e5 # No-RlsGroup\n        #  - 7357cf5161efbf8c4d5d0c30b4815ee2 # Obfuscated\n        #  - 5c44f52a8714fdd79bb4d98e2673be1f # Retags\n        #  - f537cf427b64c38c8e36298f657e4828 # Scene\n        assign_scores_to:\n          - name: HD Bluray + WEB (GER)\n\n      ### Movie Versions\n      - trash_ids:\n        # Uncomment any of the following lines to prefer these movie versions\n        #  - 570bc9ebecd92723d2d21500f4be314c # Remaster\n        #  - eca37840c13c6ef2dd0262b141a5482f # 4K Remaster\n        #  - e0c07d59beb37348e975a930d5e50319 # Criterion Collection\n        #  - 9d27d9d2181838f76dee150882bdc58c # Masters of Cinema\n        #  - db9b4c4b53d312a3ca5f1378f6440fc9 # Vinegar Syndrome\n        #  - 957d0f44b592285f26449575e8b1167e # Special Edition\n        #  - eecf3a857724171f968a66cb5719e152 # IMAX\n        #  - 9f6cbff8cfe4ebbc1bde14c7b7bec0de # IMAX Enhanced\n        assign_scores_to:\n          - name: HD Bluray + WEB (GER)\n\n      ### Others\n      - trash_ids:\n        # - 839bea857ed2c0a8e084f3cbdbd65ecb # Uncomment this line to allow HDR/DV x265 HD releases\n        assign_scores_to:\n          - name: HD Bluray + WEB (GER)\n\n      - trash_ids:\n        #  - dc98083864ea246d05a42df0d05f81cc # Uncomment this line to allow any x265 HD releases\n        #  - e6886871085226c3da1830830146846c # Uncomment this line to allow Generated Dynamic HDR\n        assign_scores_to:\n          - name: HD Bluray + WEB (GER)\n            score: 0\n```\n\n2. For this example, we try to implement `HD Bluray + WEB`.\n\n```yml title=\"only templates\"\n# ...existing code...\nradarr:\nhd-bluray-web-ger:\n  # ...\n  include:\n    - template: radarr-quality-definition-movie\n    - template: radarr-custom-formats-hd-bluray-web\n    - template: radarr-quality-profile-hd-bluray-web\n```\n\n</details>\n\n<details>\n  <summary>Write your own profiles</summary>\n\nInstead of using existing templates, you can create them yourself and use custom formats from TRaSH (or define your own if required, see [CustomFormatDefinition](./configuration/config-file.md)).\nAs a starting point, you can use templates from Recyclarr and modify them as required.\n[Recyclarr Github](https://github.com/recyclarr/config-templates/tree/master/radarr).\n\nFor this example, we try to implement an `Anime` profile.\nCheck every dir from the includes for anime-related content: CustomFormats, Definition, and Profile.\nCopy those into the config.\n\n```yml\n# ...existing code...\nradarr:\n  instance1:\n    custom_formats:\n      # Scores from TRaSH json\n      - trash_ids:\n          # Anime CF/Scoring\n          - fb3ccc5d5cc8f77c9055d4cb4561dded # Anime BD Tier 01 (Top SeaDex Muxers)\n          - 66926c8fa9312bc74ab71bf69aae4f4a # Anime BD Tier 02 (SeaDex Muxers)\n          - fa857662bad28d5ff21a6e611869a0ff # Anime BD Tier 03 (SeaDex Muxers)\n          - f262f1299d99b1a2263375e8fa2ddbb3 # Anime BD Tier 04 (SeaDex Muxers)\n          - ca864ed93c7b431150cc6748dc34875d # Anime BD Tier 05 (Remuxes)\n          - 9dce189b960fddf47891b7484ee886ca # Anime BD Tier 06 (FanSubs)\n          - 1ef101b3a82646b40e0cab7fc92cd896 # Anime BD Tier 07 (P2P/Scene)\n          - 6115ccd6640b978234cc47f2c1f2cadc # Anime BD Tier 08 (Mini Encodes)\n          - 8167cffba4febfb9a6988ef24f274e7e # Anime Web Tier 01 (Muxers)\n          - 8526c54e36b4962d340fce52ef030e76 # Anime Web Tier 02 (Top FanSubs)\n          - de41e72708d2c856fa261094c85e965d # Anime Web Tier 03 (Official Subs)\n          - 9edaeee9ea3bcd585da9b7c0ac3fc54f # Anime Web Tier 04 (Official Subs)\n          - 22d953bbe897857b517928f3652b8dd3 # Anime Web Tier 05 (FanSubs)\n          - a786fbc0eae05afe3bb51aee3c83a9d4 # Anime Web Tier 06 (FanSubs)\n          - b0fdc5897f68c9a68c70c25169f77447 # Anime LQ Groups\n          - c259005cbaeb5ab44c06eddb4751e70c # v0\n          - 5f400539421b8fcf71d51e6384434573 # v1\n          - 3df5e6dfef4b09bb6002f732bed5b774 # v2\n          - db92c27ba606996b146b57fbe6d09186 # v3\n          - d4e5e842fad129a3c097bdb2d20d31a0 # v4\n          - 06b6542a47037d1e33b15aa3677c2365 # Anime Raws\n          - 9172b2f683f6223e3a1846427b417a3d # VOSTFR\n          - b23eae459cc960816f2d6ba84af45055 # Dubs Only\n\n          # Anime Streaming Services\n          - 60f6d50cbd3cfc3e9a8c00e3a30c3114 # VRV\n\n          # Main Guide Remux Tier Scoring\n          - 3a3ff47579026e76d6504ebea39390de # Remux Tier 01\n          - 9f98181fe5a3fbeb0cc29340da2a468a # Remux Tier 02\n          - 8baaf0b3142bf4d94c42a724f034e27a # Remux Tier 03\n\n          # Main Guide WEB Tier Scoring\n          - c20f169ef63c5f40c2def54abaf4438e # WEB Tier 01\n          - 403816d65392c79236dcb6dd591aeda4 # WEB Tier 02\n          - af94e0fe497124d1f9ce732069ec8c3b # WEB Tier 03\n        assign_scores_to:\n          - name: Anime\n\n    # if no anime use default\n    quality_definition:\n      type: movie\n\n    quality_profiles:\n      - name: Anime\n        reset_unmatched_scores:\n          enabled: true\n        upgrade:\n          allowed: true\n          until_quality: Remux-1080p\n          until_score: 10000\n        min_format_score: 100\n        score_set: anime-radarr\n        quality_sort: top\n        qualities:\n          - name: Remux-1080p\n            qualities:\n              - Bluray-1080p\n              - Remux-1080p\n          - name: WEB 1080p\n            qualities:\n              - WEBDL-1080p\n              - WEBRip-1080p\n              - HDTV-1080p\n          - name: Bluray-720p\n          - name: WEB 720p\n            qualities:\n              - WEBDL-720p\n              - WEBRip-720p\n              - HDTV-720p\n          - name: Bluray-576p\n          - name: Bluray-480p\n          - name: WEB 480p\n            qualities:\n              - WEBDL-480p\n              - WEBRip-480p\n          - name: DVD\n          - name: SDTV\n```\n\n</details>\n\n### Adjusting provided templates by TRaSH-Guides/Recyclarr\n\nIt is common to use existing templates from TRaSH-Guides or Recyclarr and modify them with either your own scores or additional custom formats.\nConfigarr supports both use cases and allows adding custom formats when needed.\n\n```yaml\n# Define a new custom format (can also be done via file)\ncustomFormatDefinitions:\n  - trash_id: example-in-config-cf\n    trash_scores:\n      default: 10000\n    trash_description: \"Language: German Only\"\n    name: \"Language: Not German\"\n    includeCustomFormatWhenRenaming: false\n    specifications:\n      - name: Not German Language\n        implementation: LanguageSpecification\n        negate: true\n        required: false\n        fields:\n          value: 4\n\nsonarr:\n  instance1:\n    base_url: !secret sonarr_url\n    api_key: !secret sonarr_apikey\n\n    include:\n      # Use existing templates from Recyclarr as base\n      - template: sonarr-quality-definition-series\n      - template: sonarr-v4-quality-profile-web-1080p\n      - template: sonarr-v4-custom-formats-web-1080p\n\n      # HINT: To use TRaSH-Guides templates, you can use them too\n      #- template: d1498e7d189fbe6c7110ceaabb7473e6\n      #  source: TRASH # RECYCLARR (default) or TRASH\n\n    # Adjust the custom formats as needed per profile\n    custom_formats:\n      - trash_ids:\n          - example-in-config-cf\n        quality_profiles:\n          - name: WEB-1080p # name must match with given profiles (found in Recyclarr or TRaSH-Guides)\n            # score: 0 # Uncomment this line to add custom scoring\n\n      # Overwrite existing scores\n      - trash_ids:\n          - e6258996055b9fbab7e9cb2f75819294 # WEB Tier 01\n        quality_profiles:\n          - name: WEB-1080p # name must match with given profiles (found in Recyclarr or TRaSH-Guides)\n            score: 123\n```\n\n### Using templates from TRaSH-Guides/Recyclarr but different names\n\n- renaming quality profiles. How to implement see here: [Renaming Feature](./configuration/config-file.md#quality-profile-rename)\n- cloning quality profiles. How to implement see here: [Cloning Feature](./configuration/config-file.md#quality-profile-clone)\n- duplicate templates:\n  You can copy those templates and paste them into a locally mounted folder.\n  Then you can rename them in the templates as required.\n\n```yaml\n# The path in the container for your templates for copy & paste templates with slight modifications in the files.\nlocalConfigTemplatesPath: /app/templates\n\nsonarr:\n  instance1:\n    base_url: !secret sonarr_url\n    api_key: !secret sonarr_apikey\n\n    include:\n      # Assuming we copied 3 templates for quality definition, profile, and formats to those file names (file ending .yml)\n      - template: my-local-quality-definition-series\n      - template: my-local-quality-profile\n      - template: my-local-custom-formats\n\n      # HINT: To use TRaSH-Guides templates, you can use them too\n      #- template: d1498e7d189fbe6c7110ceaabb7473e6\n      #  source: TRASH # RECYCLARR (default) or TRASH\n\n    # Adjust the custom formats as needed per profile\n    custom_formats:\n      # Overwrite existing scores\n      - trash_ids:\n          - e6258996055b9fbab7e9cb2f75819294 # WEB Tier 01\n        quality_profiles:\n          - name: MyLocalProfile # name must match with given profiles (found in Recyclarr or TRaSH-Guides)\n            score: 123\n```\n\n## Code Examples\n\n### Full Example\n\nA complete example demonstrating all Configarr features is available in our GitHub repository. This example includes:\n\n- Docker Compose configuration\n- Complete Sonarr and Radarr setup\n- Custom format configurations\n- Quality profile settings\n- Template usage\n\nYou can find the full example at: [configarr/examples/full](https://github.com/raydak-labs/configarr/tree/main/examples/full)\n\n#### Quick Start with the Full Example\n\n1. Start the Arr containers:\n\n   ```bash\n   docker-compose up -d\n   ```\n\n   This will:\n   - Create required networks\n   - Launch \\*Arr instances\n   - Configure API keys using provided XML configs\n\n2. Run Configarr:\n   ```bash\n   docker-compose -f docker-compose.jobs.yml run --rm configarr\n   ```\n\n#### Access Points\n\nOnce running, you can access the services at:\n\n- Sonarr: http://localhost:6500\n- Radarr: http://localhost:6501\n- Other instances check `docker-compose.yml`\n\n#### Cleanup\n\nTo remove all containers and volumes:\n\n```bash\ndocker-compose down -v\n```\n\n### Scheduled Example\n\nThis is an example of how to execute Configarr in a scheduled manner.\n\nYou can find the full example at: [configarr/examples/scheduled](https://github.com/raydak-labs/configarr/tree/main/examples/scheduled)\n\nPlease check the documentation for how to configure and use the variants.\n\n### Store execution logs\n\nYou can run configarr in different environments and different toolings which do or do not provide logging out of the box.\n\n- If you are running with kubernetes Jobs you can keep the last `x` Job logs and therefore keep the logs of the executions easily.\n- If you are running with the scheduler `ofelia` there the logs are also persistet in the ofelia job logs.\n- If you are running just from docker logs can get lost. So we provide some examples how you can store them in files:\n  - `Cron`: If you are running with cron in theory all logs are stored somewhere in cron. But we can also redirect runs into files and keep them persistent:\n\n    ```sh\n    # Assuming you are in the folder with a configured compose. Redirect error logs and normal logs\n    docker compose run --rm configarr >> configarr.log 2>&1\n\n    # truncating file to last 1000 lines\n    tail -n 1000 configarr.log > configarr.log.new\n    rm configarr.log\n    mv configarr.log.new configarr.log\n    ```\n"
  },
  {
    "path": "docs/docs/faq.md",
    "content": "---\nsidebar_position: 7\ndescription: \"FAQ for configarr and common problems\"\nkeywords: [configarr, faq, troubleshoot]\n---\n\n# FAQ\n\nSometimes, you might encounter unexpected errors. Before reporting an issue, please try the following steps:\n\n## Fresh Install\n\nIf you're experiencing strange issues, a fresh install can often resolve them. This involves removing existing data and caches.\n\n- remove all folders (what you have defined in your compose or depending on your setup)\n- rerun Configarr which recreates cache folders and more\n\n## Custom TRaSH-Guide or Recyclarr Template URLs\n\nIf you have configured a custom `trashGuideUrl` or `recyclarrUrl`, caching issues might occur. In this case, clearing the cache folders is recommended.\n\n- remove the folders for the caches\n- rerun Configarr\n\n## Error: \"Recv failure: Connection reset by peer\"\n\nIf you see an error like:\n\n```\nGitResponseError: unable to access 'https://github.com/recyclarr/config-templates/': Recv failure: Connection reset by peer\n```\n\nThis can be caused by Docker bridge network issues, especially with DNS, TLS, or MTU settings. The problem may only occur when using Docker's default bridge network, and not with `network_mode: host`.\n\nReference issues:\n\n- https://github.com/raydak-labs/configarr/issues/300 (thanks for @jtnqr for helping trouble shooting)\n\n### Troubleshooting Steps\n\n- Try running Configarr with `network_mode: host` in your Docker Compose file. This often resolves the issue immediately.\n- Check your DNS and MTU settings if you use custom networking (VPN, Pi-hole, etc.).\n- If you have a VPN or custom DNS setup, try disabling them temporarily to see if the problem persists.\n- You can also manually clone the repo inside the container to verify if plain `git` works:\n\n  ```sh\n  docker exec -it <container_name> sh\n  git clone https://github.com/recyclarr/config-templates/\n  # also check git pull\n  ```\n\n### Example Docker Compose workaround\n\n```yaml\nservices:\n\tconfigarr:\n\t\tnetwork_mode: host\n\t\t# ...other options\n```\n\n> **Note:** Using `network_mode: host` is generally safe for Configarr, as it does not expose ports by default. However, be aware of your environment's security requirements.\n\n## DNS Resolution Problems (Could not resolve host: github.com)\n\nIf you encounter errors like:\n\n```\nGitError: Cloning into '/app/repos/trash-guides'...\nfatal: unable to access 'https://github.com/TRaSH-Guides/Guides/': Could not resolve host: github.com\n```\n\nThis is usually caused by DNS issues inside the Docker container. Even if DNS works on your host and inside the container when tested manually, the error may still occur intermittently for git operations.\n\n### Troubleshooting Steps\n\n- Test DNS resolution inside the container:\n  ```sh\n  docker run --rm -it busybox nslookup github.com\n  ```\n- Try changing your DNS server to a public DNS (e.g., 1.1.1.1 or 8.8.8.8) on your host or in your Docker configuration.\n- Avoid using ISP DNS servers, as they may cause intermittent failures.\n- Restart the container after changing DNS settings.\n- If you use custom Docker networks, test with the default bridge or host network.\n\nReference issues:\n\n- https://github.com/raydak-labs/configarr/issues/266\n- https://github.com/raydak-labs/configarr/issues/188\n\n## Git Clone Errors with Legacy Kernels\n\nIf you encounter errors like:\n\n```\nError: Unable to checkout revision 'master' from 'https://github.com/recyclarr/config-templates'. The revision may not exist in the repository. Error: index-pack failed\n```\n\nor\n\n```\nFunction not implemented\n```\n\nThis can be caused by legacy kernels that don't support modern git clone options like `--filter=blob:none` or sparse checkout features.\n\n### Solution\n\nSet the `CONFIGARR_DISABLE_GIT_CLONE_OPTIONS` environment variable to disable these advanced git clone options:\n\n**Docker Compose:**\n\n```yaml\nservices:\n  configarr:\n    environment:\n      - CONFIGARR_DISABLE_GIT_CLONE_OPTIONS=1\n    # ...other options\n```\n\n**Docker run:**\n\n```bash\ndocker run -e CONFIGARR_DISABLE_GIT_CLONE_OPTIONS=1 ...\n```\n\n**Kubernetes:**\n\n```yaml\nenv:\n  - name: CONFIGARR_DISABLE_GIT_CLONE_OPTIONS\n    value: \"1\"\n```\n\nWhen this variable is set, Configarr will perform standard git clones without the `--filter=blob:none` or `--sparse` options, which should work with older kernel versions.\n\nIf your kernel is very old and the error still happens (for example `unable to get random bytes for temporary file: Function not implemented`), use the `alpine3.22` image tag which pins the Alpine base for compatibility:\n\n```yaml\nservices:\n  configarr:\n    image: ghcr.io/raydak-labs/configarr:<version>-alpine3.22\n```\n\nExample:\n\n```yaml\nimage: ghcr.io/raydak-labs/configarr:v1.24.0-alpine3.22\n```\n\nReference issues:\n\n- https://github.com/raydak-labs/configarr/issues/367\n\n## TRaSH-Guides Breaking Changes (Feb 2026) {#trash-guides-breaking-changes-2026-02}\n\n:::warning Breaking Change\nTRaSH-Guides made significant changes to their JSON structure in February 2026.\nThis affects both CF group semantics and quality profile ordering.\n:::\n\n**Commit:** [2994a7979d8036a7908a92e2cd286054fd4fcc1b](https://github.com/TRaSH-Guides/Guides/commit/2994a7979d8036a7908a92e2cd286054fd4fcc1b)\n\n### What Changed\n\n**1. CF Groups: `exclude` → `include` semantics**\n\n```json\n// OLD: CFs applied to ALL profiles EXCEPT listed ones\n\"quality_profiles\": {\n  \"exclude\": { \"HD Bluray + WEB\": \"...\" }\n}\n\n// NEW: CFs applied ONLY to listed profiles\n\"quality_profiles\": {\n  \"include\": { \"Remux + WEB 1080p\": \"...\" }\n}\n```\n\n**2. Quality Profile Items: API order → Display order**\n\n- **Before**: Quality items in API response order (internal)\n- **After**: Quality items in display order (highest-to-lowest priority)\n\n### Backward Compatibility\n\n**Option 1: Pin to TRaSH-Guides revision (recommended for stability)**\n\nFor Configarr versions **< 1.22.0**, pin to the last commit before the changes:\n\n```yaml\n# config.yml\ntrashRevision: d2105f2e1b04c7e30ae44e73d682298609438216\n```\n\nThis uses the old TRaSH-Guides structure without needing the compatibility flag.\n\n**Option 2: Use compatibility flag (Configarr >= 1.22.0)**\n\nFor Configarr versions **>= 1.22.0**, use the new TRaSH-Guides structure with the compatibility flag if needed:\n\n```yaml\n# config.yml\n# Enable for old TRaSH-Guides behavior (before Feb 2026)\ncompatibilityTrashGuide20260219Enabled: true\n```\n\n| Setting           | CF Groups           | Quality Ordering            |\n| ----------------- | ------------------- | --------------------------- |\n| `false` (default) | `include` semantics | Display order (no reversal) |\n| `true`            | `exclude` semantics | API order (with reversal)   |\n\n:::info Temporary Flag\nThe `compatibilityTrashGuide20260219Enabled` flag is **temporary** and will be removed in a future version.\nIt's recommended to update to the latest TRaSH-Guides version.\n:::\n\n### Version Matrix\n\n| Configarr Version | TRaSH-Guides Default | Old Behavior Via                                                                                            |\n| ----------------- | -------------------- | ----------------------------------------------------------------------------------------------------------- |\n| < 1.22.0          | Old structure        | N/A (default)                                                                                               |\n| >= 1.22.0         | New structure        | `compatibilityTrashGuide20260219Enabled: true` OR `trashRevision: d2105f2e1b04c7e30ae44e73d682298609438216` |\n"
  },
  {
    "path": "docs/docs/installation/_category_.json",
    "content": "{\n  \"label\": \"Installation\",\n  \"position\": 3,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"description\": \"Learn how to install Configarr\"\n  }\n}\n"
  },
  {
    "path": "docs/docs/installation/_include/docker-basic-conf.yml",
    "content": "#trashGuideUrl: https://github.com/BlackDark/fork-TRASH-Guides\n#recyclarrConfigUrl: https://github.com/BlackDark/fork-recyclarr-configs\nlocalCustomFormatsPath: /app/cfs\nlocalConfigTemplatesPath: /app/templates\n\ncustomFormatDefinitions:\n  - trash_id: example-in-config-cf\n    trash_scores:\n      default: -10000\n    trash_description: \"Language: German Only\"\n    name: \"Language: Not German\"\n    includeCustomFormatWhenRenaming: false\n    specifications:\n      - name: Not German Language\n        implementation: LanguageSpecification\n        negate: true\n        required: false\n        fields:\n          value: 4\n\nsonarr:\n  instance1:\n    # Set the URL/API Key to your actual instance\n    base_url: http://sonarr:8989\n    api_key: !secret SONARR_API_KEY\n\n    quality_definition:\n      type: series\n\n    include:\n      #### Custom\n      - template: sonarr-cf # template name\n      - template: sonarr-quality\n\n    custom_formats:\n      # Movie Versions\n      - trash_ids:\n          - 9f6cbff8cfe4ebbc1bde14c7b7bec0de # IMAX Enhanced\n        quality_profiles:\n          - name: ExampleProfile\n            # score: 0 # Uncomment this line to disable prioritised IMAX Enhanced releases\n\nradarr: {} # no radarr instance\n"
  },
  {
    "path": "docs/docs/installation/binary.md",
    "content": "---\nsidebar_position: 4\ntitle: Binary\ndescription: \"Run configarr as binary file\"\nkeywords: [configarr binary, configarr executable]\n---\n\nimport Admonition from \"@theme/Admonition\";\nimport CodeBlock from \"@theme/CodeBlock\";\nimport DockerBasicConf from \"!!raw-loader!./\\_include/docker-basic-conf.yml\";\n\n# Executable / Binary <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.17.0</span>\n\nWith <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.17.0</span> we have started to distribute configarr additionally as binary files.\nThis is currently based on [Bun Compilation](https://bun.com/docs/bundler/executables)\n\nIf you have problems or feedback feel free to create an issue to discuss potential problems or optimizations.\n\n## Quick Start\n\nCheckout the releases in [Github](https://github.com/raydak-labs/configarr/releases/).\nThere we will attach binaries for different architectures and systems.\n\nThe files are probably compressed and need to be extracted.\nAfterwards you can run the executable as any other program or tool.\n\nNeed more help? [open an issue](https://github.com/raydak-labs/configarr/issues).\n\n## FAQ\n\n- `On MacOS if I download files the file is corrupt`: If downloaded via browser or something the resulting executable can be quarantined.\n  - To check attributes: `xattr ./configarr -l`\n  - To remove quarantine: `xattr -dr com.apple.quarantine ./configarr`\n"
  },
  {
    "path": "docs/docs/installation/docker.md",
    "content": "---\nsidebar_position: 1\ntitle: Docker Installation\ndescription: \"Learn how to install and configure Configarr using Docker\"\nkeywords: [configarr docker, docker installation, docker setup, configarr configuration]\n---\n\nimport CodeBlock from \"@theme/CodeBlock\";\nimport DockerBasicConf from \"!!raw-loader!./\\_include/docker-basic-conf.yml\";\n\n# Docker Installation\n\nThis guide will walk you through setting up Configarr using Docker.\n\n:::tip\nFor quick starting and testing you can use the `latest` tag.\nBut if you are ready and finished switch to fixed tag like `1.9.0` so you can update and do required changes if we release new versions.\nSolutions like `Renovate` are good for keeping your dependencies updated.\n:::\n\n## Quick Start\n\nThe fastest way to get started is using the official Docker image:\n\n```bash title=\"shell\"\ndocker run -d \\\n  --name=configarr \\\n  -v /path/to/config:/config \\\n  ghcr.io/raydak-labs/configarr:latest\n\n# Or use dockerhub image:\n\ndocker run -d \\\n  --name=configarr \\\n  -v /path/to/config:/config \\\n  configarr/configarr:latest\n```\n\n## Docker Compose (Recommended)\n\nFor a more maintainable setup, we recommend using Docker Compose:\n\n```yaml title=\"compose.yml\"\n#version: \"3.8\"\nservices:\n  configarr:\n    image: ghcr.io/raydak-labs/configarr:latest\n    container_name: configarr\n    #user: 1000:1000 # Optional, defaults to root:root\n    environment:\n      - TZ=Etc/UTC\n    volumes:\n      - ./config:/app/config # Contains the config.yml and secrets.yml\n      - ./dockerrepos:/app/repos # Cache repositories\n      - ./custom/cfs:/app/cfs # Optional if custom formats locally provided\n      - ./custom/templates:/app/templates # Optional if custom templates\n    # restart: \"no\" # optional make sure this is set to no or removed. Default is no\n```\n\nSave this as `docker-compose.yml` and run:\n\n```bash title=\"shell\"\ndocker-compose run --rm configarr\n```\n\n## Configuration\n\n### Volume Mappings\n\n| Volume           | Description                                                                           |\n| ---------------- | ------------------------------------------------------------------------------------- |\n| `/app/config`    | Contains all configuration files and data. Can be changed with Environment Variables. |\n| `/app/repos`     | Contains cached repos. Can be changed with Environment Variables.                     |\n| `/app/cfs`       | Contains custom cfs. Can be changed with Environment Variables.                       |\n| `/app/templates` | Contains templates. Can be changed with Environment Variables.                        |\n\n### Environment Variables\n\nSee [Environment Variables](../configuration/environment-variables.md)\n\n## Basic Configuration\n\nCreate a configuration file at `/path/to/config/config.yml` more information about the config file can be found [here](../configuration/config-file.md).\nYou can also test everything with the [Full Example](../examples.md) locally.\n\n<details>\n  <summary>Very basic configuration</summary>\n  <CodeBlock language=\"yml\">{DockerBasicConf}</CodeBlock>\n</details>\n\n## Updating\n\nTo update to the latest version:\n\n```bash title=\"shell\"\ndocker-compose pull\ndocker-compose run --rm configarr\n```\n\n## Troubleshooting\n\nRunning the container will output logs to the console.\nWith those you can see what is happening and if there are any issues.\nIncrease the log level with the `LOG_LEVEL` environment variable to get more detailed logs.\n\n### Common Issues\n\n- **Permission Issues**\n  - Ensure user matches your required user\n  - Check folder permissions on the config directory\n  - after changing the user, adjust the user in the git repos (TRaSH-Guides, recyclarr) to match\n\n- **Connection Issues**\n  - Verify Sonarr/Radarr URLs are accessible from the container\n  - Confirm API keys are correct\n  - Check network connectivity between containers if using Docker networks\n\n- **Configuration Issues**\n  - Validate your YAML syntax\n  - Ensure all required fields are present in config.yaml\n\n- **Container restarting**\n  - Ensure you have not set restart policies and running with `docker-compose up -d`. This triggers the docker daemon to restart the container every minute.\n  - Scheduling is NOT implemented into configarr as described [here](../configuration/scheduled.md). Therefore please check the [Scheduled example](../examples.md)\n\nNeed more help? [open an issue](https://github.com/raydak-labs/configarr/issues).\n"
  },
  {
    "path": "docs/docs/installation/kubernetes.md",
    "content": "---\nsidebar_position: 2\ntitle: Kubernetes Installation\ndescription: \"Learn how to install and configure Configarr using Kubernetes\"\nkeywords: [configarr kubernetes, kubernetes installation, kubernetes setup, configarr configuration]\n---\n\n# Kubernetes Installation Guide\n\nThis guide will help you deploy Configarr in a Kubernetes environment. Configarr can be run as a CronJob to periodically sync your configurations.\n\n## Prerequisites\n\n- A working Kubernetes cluster\n- `kubectl` configured to access your cluster\n- Basic understanding of Kubernetes resources (ConfigMaps, Secrets, CronJobs)\n\n## Installation Steps\n\n### 1. Create the Configuration Files\n\nFirst, create `config.yml` and choose how to provide sensitive values:\n\n- `config.yml` - Your main Configarr configuration (required)\n- Environment variables via Kubernetes `Secret` + `!env` in `config.yml` (recommended)\n- `secrets.yml` + `!secret` in `config.yml` (optional alternative)\n\nFor detailed configuration options, see the [Configuration Guide](../configuration/config-file.md).\n\n### 2. Deploy to Kubernetes\n\nBelow is a complete example of the necessary Kubernetes resources. Save this as `configarr.yaml`:\n\n```yaml title=\"configarr.yaml\"\n---\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: configarr\nspec:\n  schedule: \"0 * * * *\" # Runs every hour\n  successfulJobsHistoryLimit: 1\n  failedJobsHistoryLimit: 1\n  jobTemplate:\n    spec:\n      template:\n        spec:\n          containers:\n            - name: configarr\n              image: ghcr.io/raydak-labs/configarr:latest\n              imagePullPolicy: Always\n              tty: true # for color support\n              envFrom:\n                - configMapRef:\n                    name: common-deployment-environment\n                - secretRef:\n                    name: configarr-env\n              volumeMounts:\n                - mountPath: /app/repos # Cache repositories\n                  name: app-data\n                  subPath: configarr-repos\n                - name: config-volume # Mount specific config\n                  mountPath: /app/config/config.yml\n                  subPath: config.yml\n          volumes:\n            - name: app-data\n              persistentVolumeClaim:\n                claimName: media-app-data\n            - name: config-volume\n              configMap:\n                name: configarr\n          restartPolicy: Never\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: configarr-env\ntype: Opaque\nstringData:\n  SONARR_API_KEY: \"your-sonarr-api-key-here\"\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: configarr\ndata:\n  config.yml: |\n    trashGuideUrl: https://github.com/TRaSH-Guides/Guides\n    recyclarrConfigUrl: https://github.com/recyclarr/config-templates\n\n    sonarr:\n      series:\n        base_url: http://sonarr:8989\n        api_key: !env SONARR_API_KEY\n\n        quality_definition:\n          type: series\n\n        include:\n          # WEB-1080p\n          - template: sonarr-quality-definition-series\n          - template: sonarr-v4-quality-profile-web-1080p\n          - template: sonarr-v4-custom-formats-web-1080p\n\n          # WEB-2160p\n          - template: sonarr-v4-quality-profile-web-2160p\n          - template: sonarr-v4-custom-formats-web-2160p\n\n        custom_formats: []\n    radarr: {}\n```\n\n### 3. Deploy the Resources\n\nApply the configuration to your cluster:\n\n```bash title=\"shell\"\nkubectl apply -f configarr.yaml\n```\n\n## Configuration Details\n\n### CronJob Configuration\n\n- `schedule`: Set how often Configarr should run (default: hourly)\n- `successfulJobsHistoryLimit` and `failedJobsHistoryLimit`: Control how many completed/failed jobs to keep\n\n### Volume Mounts\n\n1. **Repository Cache** (`/app/repos`):\n   - Persists downloaded repositories to avoid repeated downloads\n   - Requires a PersistentVolumeClaim\n\n2. **Configuration** (`/app/config/config.yml`):\n   - Main configuration file mounted from ConfigMap\n   - See [Configuration Guide](../configuration/config-file.md) for options\n\n3. **Environment Variables** (`envFrom.secretRef`):\n   - Sensitive data loaded from Kubernetes Secret\n   - Referenced with `!env` in `config.yml`\n   - Avoids duplicating API keys in both Kubernetes Secrets and a mounted `secrets.yml`\n\n### Security Considerations\n\n- Store sensitive information in Kubernetes Secrets\n- Use `!env` in `config.yml` to read values injected from Kubernetes Secrets\n- If you prefer mounted secret files, `!secret` with `secrets.yml` is still supported\n- Consider using sealed secrets or external secret management solutions\n\n## Alternative Deployment Options\n\nIf Kubernetes is not suitable for your environment, consider:\n\n- [Docker Installation](docker.md) for simpler containerized deployment\n- Running directly on the host system\n\n## Troubleshooting\n\n1. Check the CronJob logs:\n\n   ```bash\n   kubectl get pods | grep configarr\n   kubectl logs <pod-name>\n   ```\n\n2. Verify your secret-backed environment variables are present:\n\n   ```bash\n   kubectl describe pod <pod-name>\n   ```\n\n3. Ensure your PersistentVolumeClaim is bound:\n   ```bash\n   kubectl get pvc\n   ```\n\nFor more detailed configuration options, refer to the [Configuration Guide](../configuration/config-file.md).\n"
  },
  {
    "path": "docs/docs/installation/third-party.md",
    "content": "---\nsidebar_position: 3\ntitle: Third party\ndescription: \"Learn how to install and configure Configarr in third party services.\"\nkeywords: [configarr docker, docker installation, docker setup, unraid]\n---\n\n# Third partys\n\nThis guide will walk you through setting up Configarr in 3rd party services.\n\n:::tip\nAs this is new and you are missing some services feel free to create a PR!\n\nContributions welcome!\n:::\n\n## Proxmox VE Helper-Scripts\n\n<div style={{ textAlign: \"center\" }}>\n  <img height=\"200\" src=\"https://community-scripts.github.io/ProxmoxVE/logo.png\" title=\"Proxmox Helper logo\" alt=\"Logo from Proxmox Helper Scripts\" />\n</div>\n\nThanks to Community User @finkerle we have now an installation script for Proxmox users!\nWith the script you can install & update the configarr version in your proxmox instance automatically.\nThe instance will be deployed as an LXC Container.\n\nCheck it out here [Configarr Proxmox Helper](https://community-scripts.github.io/ProxmoxVE/scripts?id=configarr).\n\n## Unraid {#unraid}\n\n:::tip\nExisting apps in Unraid CA are not maintained by us!\nIf donating it is not directed to us! Please check configarr Github pages if you want to donate.\nContributions welcome!\n:::\n\nSetting up in Unraid with docker is straigth forward and combined with `ofelia` we can schedule the containers easily.\n\n_HINT_: The provided Apps in Unraid are not maintained by us!\n\nMake sure to enable Advanced/extended view in Unraid (top right).\n\n- Configarr:\n\n  ```\n  Name: configarr (we need this later on)\n  Repository: configarr/configarr:latest # Recommendation: use tags like 1.9.0\n\n  (add volume mappings like your setups requires it. Example with <name> - <host/unraid path>:<container path>)\n  Config volume - /mnt/user/appdata/configarr/config:/app/config\n  Repo cache - /mnt/user/appdata/configarr/repos:/app/repos\n  Custom formats - /mnt/user/appdata/configarr/cfs:/app/cfs\n  Templates - /mnt/user/appdata/configarr/templates:/app/templates\n  ```\n\n  - Add other variables or mapping as your setup requires it\n  - Afterwards create the required files in the config volume `config.yml` and `secrets.yml` (check examples or this guide)\n\n- Ofelia (scheduler):\n\n  ```\n  Name: ofelia\n  Repository: mcuadros/ofelia:latest # Recommendation: use specific tags not latest\n  Post Arguments: daemon --config=/opt/config.ini\n\n  (add volume mappings like your setups requires it. Example with <name> - <host/unraid path>:<container path>)\n  Docker socket - /var/run/docker.sock:/var/run/docker.sock (Read Only)\n  Ofelia config file - /mnt/user/appdata/ofelia/ofelia.ini:/opt/config.ini (Read only)\n  ```\n\n  - Make sure to create the `ofelia.ini` file best before starting the container\n\n  ```ini\n  [job-run \"run-configarr-existing-container\"]\n  schedule = @every 10s # adjust as required. Recommendation every 3h or so\n  container = configarr # this is the name of container we gave\n  ```\n\n  - you can also activate `autostart` for ofelia\n\n![Unraid Setup with the containers](_images/unraid_setup.webp)\n\nNow start both containers.\nCheck the logs if configarr works as expected (exit code should be 0).\nOfelia should keep running and restarting the configarr in your defined interval.\n\n**Enjoy!**\n\n## Synology NAS {#synology}\n\nFor scheduled runs on Synology you can use the [Task Scheduler](https://kb.synology.com/en-au/DSM/help/DSM/AdminCenter/system_taskscheduler?version=7) in order to run configarr in a cron way.\nTo configure a scheduled task in DSM 7.2 you go to Control Panel - Services - Task Scheduler. From there you can create a new Scheduled Task (User-defined script).\nAs Synology requires root permission to run docker containers, \"root\" should be chosen as the user. Then within the Schedule tab you can choose your preferred frequency to run configarr.\nFor the actual user-defined script you just input the docker run command you also use in an interactive terminal, but be sure to NOT include `sudo` in your command (as your already run the command with root permissions). Like so:\n\n```\ndocker run -d --rm --name=configarr -e TZ=[YOUR-TIMEZONE] -v /[SYNOLOGY-VOLUME]/[SYNOLOGY-SHARED-FOLDER-OF-YOUR-DOCKER-CONTAINERS]/[CONFIGARR-SUBFOLDER]:/app/config ghcr.io/raydak-labs/configarr:[REQUIRED-VERSION]\n```\n\nFor example:\n\n```\ndocker run -d --rm --name=configarr -e TZ=Europe/Amsterdam -v /volume1/docker/configarr:/app/config ghcr.io/raydak-labs/configarr:1.12.0\n```\n\nAlternatively if you want to be able to view the logs within Synology's Container Manager after configarr has finished running, then you could remove the `--rm` flag and start the user-defined script with `docker rm configarr` so that the container is not immediately removed after it has finished running. For example:\n\n```\ndocker rm configarr\ndocker run -d --name=configarr -e TZ=Europe/Amsterdam -v /volume1/docker/configarr:/app/config ghcr.io/raydak-labs/configarr:1.12.0\n```\n\nAfter clicking \"OK\" it will ask for your password, given that you created a scheduled script with root permissions. After you're done you can perform a run manually to check if everything works by selecting the task and press \"Run\".\n\n## NixOS Module <span className=\"theme-doc-version-badge badge badge--secondary configarr-badge\">1.18.0</span> {#nixos}\n\n:::warning Experimental Feature\nNixOS module support is experimental and available from version 1.18.0 onwards.\n:::\n\nConfigarr can be run as a systemd service on NixOS using the included NixOS module.\n\n### Setup\n\nInclude the configarr input in your flake:\n\n```nix\ninputs.configarr.url = \"github:raydak-labs/configarr\";\n```\n\nThen import the module and configure the service:\n\n```nix\n{\n  config,\n  inputs,\n  ...\n}: {\n  imports = [\n    inputs.configarr.nixosModules.default\n  ];\n\n  services.configarr = {\n    config =\n      # yaml\n      ''\n        radarr:\n          radarr_instance:\n            api_key: !env RADARR_API_KEY\n            base_url: http://localhost:${toString config.services.radarr.settings.server.port}\n            media_naming:\n              folder: default\n            root_folders:\n              - /mnt/movies/English\n      '';\n    enable = true;\n    environmentFile = \"${config.sops.templates.configarr-ev.path}\";\n  };\n\n  sops = {\n    secrets = {\n      radarr-api-key.sopsFile = ./secrets/radarr-api-key;\n    };\n    templates.configarr-ev = {\n      content = ''\n        LOG_LEVEL=debug\n        LOG_STACKTRACE=true\n        RADARR_API_KEY=${config.sops.placeholder.radarr-api-key}\n      '';\n      inherit (config.services.configarr) group;\n      owner = config.services.configarr.user;\n    };\n  };\n}\n```\n\nThis configuration sets up configarr as a systemd service with proper secret management using sops-nix.\n\n### Updating to a New Version\n\nTo update configarr to a new version, you need to update both the version number and the corresponding hashes in the nix package file.\n\n1. Edit `nix/package.nix` and update the `version` field to the desired release (e.g., `\"1.18.0\"`)\n2. Update the `rev` field in `fetchFromGitHub` to match: `\"v1.18.0\"`\n3. Set both hash fields to empty strings (`hash = \"\";`)\n4. Run `nix build` - it will fail and provide you with the correct hashes\n5. Copy the hash from the error message for `fetchFromGitHub` and update the `src.hash` field\n6. Run `nix build` again - it will fail again for the pnpm dependencies\n7. Copy the hash from this error message and update the `pnpmDeps.hash` field\n8. Run `nix build` once more - it should now succeed\n\nAlternatively, you can find the source hash directly on GitHub:\n\n- Go to `https://github.com/raydak-labs/configarr/releases/tag/v[VERSION]`\n- Download the source tarball and calculate its hash using `nix hash file [downloaded-file]`\n"
  },
  {
    "path": "docs/docs/intro.mdx",
    "content": "---\nsidebar_position: 1\ndescription: \"Configarr - A powerful synchronization tool for Sonarr, Radarr and other *Arr tools that helps manage custom formats and quality profiles using TRaSH-Guides and custom configurations\"\nkeywords:\n  [\n    configarr,\n    sonarr,\n    radarr,\n    trash guides,\n    TRaSH-Guides,\n    custom formats,\n    media management,\n    automation,\n    recyclarr,\n    quality profiles,\n    whisparr,\n  ]\n---\n\n# Introduction\n\nWelcome to Configarr - your all-in-one solution for managing Sonarr and Radarr (and other \\*Arr tools) configurations with seamless integration of TRaSH-Guides and custom formats.\n\n## What is Configarr?\n\nConfigarr is a powerful configuration and synchronization tool designed specifically for Sonarr v4 and Radarr v5. It streamlines the process of managing custom formats and quality profiles by automatically synchronizing settings from TRaSH-Guides while supporting additional customizations.\n\n### Experimental support\n\nExperimental support also available for [(check experimental support)](./configuration/experimental-support.md):\n\n- Whisparr v3\n- Readarr v1\n- Lidarr v2\n\n### Key Features\n\n- 🔄 **TRaSH-Guides Integration**: Directly sync custom formats and quality profiles from TRaSH-Guides\n- 🔗 **Recyclarr Template Compatibility**: Works with existing Recyclarr templates\n- 🎯 **Custom Format Support**: Create and manage your own custom formats\n- 🌍 **Multi-language Support**: Includes specialized formats for different languages/countries\n- 🔧 **Flexible Configuration**: Multiple ways to provide custom formats:\n  - Sync from TRaSH-Guides\n  - Sync from local files\n  - Direct configuration in YAML\n  - Smart merging (TRaSH-Guide → Local Files → Config)\n\n### Why Choose Configarr?\n\nIf you're managing a media server with Sonarr and Radarr (or other \\*Arr tools), Configarr helps you:\n\n- Save time by automating configuration management\n- Maintain consistency across your media servers\n- Easily implement best practices from TRaSH-Guides\n- Customize settings for your specific needs\n- We provide custom formats for different languages/countries as long as TRaSH-Guides does not include them\n\n## Requirements\n\n- Sonarr v4\n- Radarr v5\n- check experimental support [here](./configuration/experimental-support.md)\n- Docker or Kubernetes\n\n## Getting Started\n\nReady to streamline your media server configuration? Let's get started with the basic setup.\n\n- [Continue to Installation →](/docs/category/installation/)\n- [HowTo Configuration →](./examples.md)\n- **[YouTube Setup Guide](https://www.youtube.com/watch?v=LoeCXCB89h0)**\n  :::note\n\n  **First** youtube video creation. If useful feel free to 🔔\n\n  :::\n\n<!---\nCommented\n<div className=\"youtubeContainer\">\n  <iframe\n    className=\"youtubeVideo\"\n    src=\"https://www.youtube.com/embed/LoeCXCB89h0\"\n    title=\"Configarr - Setup Guide\"\n    frameborder=\"0\"\n    allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n    referrerpolicy=\"strict-origin-when-cross-origin\"\n    allowfullscreen\n  ></iframe>\n</div>\n-->\n\n### Container Images\n\nImages are available via Github Container Registry (ghcr) and dockerhub with tags and latest (check the repos for all available tags):\n\n- GHCR: https://github.com/raydak-labs/configarr/pkgs/container/configarr\n  - `ghcr.io/raydak-labs/configarr:latest`\n- DockerHub: https://hub.docker.com/repository/docker/configarr/configarr/general\n  - `docker.io/configarr/configarr:latest` or simply `configarr/configarr:latest`\n\n### Related/Inspired Projects\n\n- [TRaSH-Guides](https://trash-guides.info/)\n- [Recyclarr](https://github.com/recyclarr/recyclarr)\n- [Notifiarr](https://notifiarr.com/)\n- [Profilarr](https://github.com/Dictionarry-Hub/profilarr)\n\n## Changes / Changelog\n\nYou can find the changes and new features in Github. See:\n\n- [Changelog](https://github.com/raydak-labs/configarr/blob/main/CHANGELOG.md)\n- or [Github Releases](https://github.com/raydak-labs/configarr/releases)\n\n:::tip\nNew to media server management? Check out our [concepts guide](./concepts.md) to understand the basics of custom formats and quality profiles.\n:::\n\n## Star History\n\nHelp us reach 1000 stars!\n\n<div style={{ textAlign: \"center\" }}>\n  <img src=\"https://api.star-history.com/svg?repos=raydak-labs/configarr&type=Date\" title=\"Github Star History\" />\n</div>\n"
  },
  {
    "path": "docs/docs/profiles/index.mdx",
    "content": "---\nsidebar_position: 5\ndescription: \"Sample profiles\"\nkeywords: [configarr, examples, configuration, profiles]\n---\n\nimport CodeBlock from \"@theme/CodeBlock\";\n\n# Quality Profiles\n\nProfiles you can copy and paste to use for your desired profiles. You can always modify and create own profiles just copy the existing configs as base and modify as wanted.\n\nFor example you like the base for the german custom formats:\n\n- you can either just adjust scorings for specific custom formats\n- or you can copy everything as base and maintain the scores without the need of a forked templates repo\n\n:::tip\nIf you are starting to setup probably start taking a look at [HowTos](../examples)\n:::\n\n## From scratch {#from-scratch}\n\nYou can start to implement and share full profiles directly in the config + custom formats.\nEverything can be done directly in your configuration without the need to fork or maintain repository.\n\nSee a quick example [here](https://gist.github.com/BlackDark/19e0c10422d7178c9e1ef1dd52a5aa70) for defining everything a profile needs\n\n## Existing base templates\n\nYou can use existing base templates (configuration files):\n\n- TRaSH\n  - using with `includes`\n- Recyclarr\n  - using with `includes` (matching includes folder)\n  - or copy&paste the `templates`/`pre-built configurations`.\n\n| Source                  |                                                Radarr                                                |                                                Sonarr                                                |\n| ----------------------- | :--------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------: |\n| **TRaSH-Guides**        | [TRaSH Radarr](https://github.com/TRaSH-Guides/Guides/tree/master/docs/json/radarr/quality-profiles) | [TRaSH Sonarr](https://github.com/TRaSH-Guides/Guides/tree/master/docs/json/sonarr/quality-profiles) |\n| **Recyclarr Templates** |         [Recyclarr Radarr](https://github.com/recyclarr/config-templates/tree/master/radarr)         |         [Recyclarr Sonarr](https://github.com/recyclarr/config-templates/tree/master/sonarr)         |\n\n:::note\nRecyclarr `includes` will be updated automatically because they are included. Recyclarr `Templates`/`Pre-Built Configuration Files` themselve _NOT_ and you have to update them if they will be changed in repository.\nYou can mostly copy&paste the pre-built files and adjust slightly to work with configarr.\n:::\n\n## Language based\n\nWe can use the existing templates provided in the TRaSH-Guides (recommended) or Recyclarr to start with a good starting base.\nAfterwards you can adjust scores as you need them to be or adopt the profile and customize it completely to your requirements.\nIf you need to know how to include those templates [see here](../examples).\n\n### English profiles\n\n| Source                  |                                     Radarr                                      |                                     Sonarr                                      |\n| ----------------------- | :-----------------------------------------------------------------------------: | :-----------------------------------------------------------------------------: |\n| **TRaSH-Guides**        | [TRaSH Radarr](https://trash-guides.info/Radarr/radarr-setup-quality-profiles/) | [TRaSH Sonarr](https://trash-guides.info/Sonarr/sonarr-setup-quality-profiles/) |\n| **Recyclarr Templates** |   [Recyclarr Radarr](https://recyclarr.dev/wiki/guide-configs/#hd-bluray-web)   |   [Recyclarr Sonarr](https://recyclarr.dev/wiki/guide-configs/#web-1080p-v4)    |\n\n### English Profiles (Anime)\n\n| Source                  |                                        Radarr                                         |                                        Sonarr                                         |\n| ----------------------- | :-----------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------: |\n| **TRaSH-Guides**        | [TRaSH Radarr](https://trash-guides.info/Radarr/radarr-setup-quality-profiles-anime/) | [TRaSH Sonarr](https://trash-guides.info/Sonarr/sonarr-setup-quality-profiles-anime/) |\n| **Recyclarr Templates** |      [Recyclarr Radarr](https://recyclarr.dev/wiki/guide-configs/#anime-radarr)       |     [Recyclarr Sonarr](https://recyclarr.dev/wiki/guide-configs/#anime-sonarr-v4)     |\n\n### German profiles\n\n| Source                  |                                          Radarr                                           |                                          Sonarr                                           |\n| ----------------------- | :---------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------: |\n| **TRaSH-Guides**        | [TRaSH Radarr](https://trash-guides.info/Radarr/radarr-setup-quality-profiles-german-en/) | [TRaSH Sonarr](https://trash-guides.info/Sonarr/sonarr-setup-quality-profiles-german-en/) |\n| **Recyclarr Templates** |    [Recyclarr Radarr](https://recyclarr.dev/wiki/guide-configs/#german-hd-bluray-web)     |   [Recyclarr Sonarr](https://recyclarr.dev/wiki/guide-configs/#german-hd-bluray-web-v4)   |\n\n### German Profiles (Anime)\n\nCurrently (as of 2025-02) no finished profiles exist for German Anime formats.\nSo you can either start with the English ones from TRaSH-Guide and modify with the German language CFs or use some existing templates from the community.\nExample for starting profiles from scratch [here](#from-scratch)\n\nCommunity configs 👥🤝🤗 :\n\n- https://github.com/nani8ot/configarr-anime\n- https://github.com/MajorPain007/homelab/tree/main/configarr\n\n:::note\nBe sure to copy all required configs and templates when using the community projects.\nAnd keep in mind that you need to keep track of possible changes on your own.\n:::\n\n## Modifications / Adjustments\n\nPlease check the [Config](../configuration/config-file) for all available options.\n\n- Reference for starting full profiles from scratch: [here](#from-scratch)\n- Adjusting the `language` of a QualityProfile.\n  TRaSH-Guide profiles has added support for the `language` in their provided JSON files.\n  This will be used if available.\n  But you can also adjust the value also in the config file.\n\n  ```yaml title=\"config.yml\"\n  radarr:\n    instance1:\n      # ...\n\n      quality_profiles:\n        - name: HD Bluray + WEB\n          # Support added with 1.7.0. Not support in all *arr applications. Check the application if it is supported.\n          language: Any # The name must match available names in *arr\n  ```\n\n- Adjusting scoring for custom formats:\n\n  ```yaml title=\"config.yml\"\n  radarr:\n    instance1:\n      # ...\n\n    custom_formats: # Custom format assignments\n      - trash_ids:\n          - 9f6cbff8cfe4ebbc1bde14c7b7bec0de # Some existing CustomFormat (this is the ID from TRaSH-Guides)\n        assign_scores_to:\n          - name: HD Bluray + WEB # The existing or new QualityProfile Name. Must match!\n            score: 0 # New scoring\n  ```\n"
  },
  {
    "path": "docs/docusaurus.config.ts",
    "content": "import type * as Preset from \"@docusaurus/preset-classic\";\nimport type { Config } from \"@docusaurus/types\";\nimport { default as lunrSearch } from \"docusaurus-lunr-search\";\nimport { themes as prismThemes } from \"prism-react-renderer\";\n\n// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)\nconst config: Config = {\n  title: \"Configarr\",\n  tagline: \"Simplified configuration management for your Arr applications like Sonarr, Radarr and more.\",\n  favicon: \"img/favicon.ico\",\n\n  // Set the production url of your site here\n  url: \"https://configarr.de\",\n  // Set the /<baseUrl>/ pathname under which your site is served\n  // For GitHub pages deployment, it is often '/<projectName>/'\n  baseUrl: \"/\",\n\n  // GitHub pages deployment config.\n  // If you aren't using GitHub pages, you don't need these.\n  organizationName: \"raydak-labs\", // Usually your GitHub org/user name.\n  projectName: \"configarr\", // Usually your repo name.\n\n  onBrokenLinks: \"throw\",\n  markdown: {\n    hooks: {\n      onBrokenMarkdownLinks: \"warn\",\n    },\n  },\n  // Even if you don't use internationalization, you can use this field to set\n  // useful metadata like html lang. For example, if your site is Chinese, you\n  // may want to replace \"en\" with \"zh-Hans\".\n  i18n: {\n    defaultLocale: \"en\",\n    locales: [\"en\"],\n  },\n\n  presets: [\n    [\n      \"classic\",\n      {\n        docs: {\n          sidebarPath: \"./sidebars.ts\",\n          // Please change this to your repo.\n          // Remove this to remove the \"edit this page\" links.\n          editUrl: \"https://github.com/raydak-labs/configarr/tree/main/docs/\",\n        },\n        blog: {\n          showReadingTime: true,\n          feedOptions: {\n            type: [\"rss\", \"atom\"],\n            xslt: true,\n          },\n          // Please change this to your repo.\n          // Remove this to remove the \"edit this page\" links.\n          editUrl: \"https://github.com/raydak-labs/configarr/tree/main/docs/\",\n          // Useful options to enforce blogging best practices\n          onInlineTags: \"warn\",\n          onInlineAuthors: \"warn\",\n          onUntruncatedBlogPosts: \"warn\",\n        },\n        theme: {\n          customCss: \"./src/css/custom.css\",\n        },\n      } satisfies Preset.Options,\n    ],\n  ],\n\n  plugins: [\n    lunrSearch,\n    () => ({\n      name: \"inject-tag\",\n      injectHtmlTags() {\n        return {\n          headTags: [\n            `<script defer data-domain=\"configarr.de\" src=\"https://plausible.raydak.de/js/script.file-downloads.hash.outbound-links.tagged-events.js\"></script>\n<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>\n`,\n          ],\n        };\n      },\n    }),\n  ],\n\n  themeConfig: {\n    // Replace with your project's social card\n    image: \"img/docusaurus-social-card.jpg\",\n    colorMode: {\n      defaultMode: \"dark\",\n    },\n    metadata: [\n      {\n        name: \"description\",\n        content: \"Configarr - Simplify your configuration management for Arr applications like Sonarr, Radarr, Readarr, Lidarr.\",\n      },\n      { name: \"keywords\", content: \"configarr, configuration, management, automation, sonarr, radarr, lidarr, recyclarr, notifiarr\" },\n      { name: \"robots\", content: \"index, follow\" },\n      { property: \"og:title\", content: \"Configarr - Configuration Management Simplified\" },\n      {\n        property: \"og:description\",\n        content: \"Easily manage and automate your configurations in Arr (Sonarr,Radarr,Lidarr,Readarr) with Configarr.\",\n      },\n    ],\n    navbar: {\n      title: \"Configarr\",\n      logo: {\n        alt: \"Configarr Logo\",\n        src: \"img/logo.svg\",\n      },\n      items: [\n        {\n          type: \"docSidebar\",\n          sidebarId: \"tutorialSidebar\",\n          position: \"left\",\n          label: \"Documentation\",\n        },\n        // { to: \"/blog\", label: \"Blog\", position: \"left\" },\n        {\n          href: \"https://github.com/raydak-labs/configarr\",\n          label: \"GitHub\",\n          position: \"right\",\n        },\n        {\n          type: \"html\",\n          position: \"right\",\n          value:\n            '<a href=\"https://github.com/raydak-labs/configarr\" target=\"_blank\"><img alt=\"GitHub Repo stars\" src=\"https://img.shields.io/github/stars/raydak-labs/configarr\"></a>',\n        },\n      ],\n    },\n    footer: {\n      style: \"dark\",\n      links: [\n        {\n          title: \"Docs\",\n          items: [\n            {\n              label: \"Intro\",\n              to: \"/docs/intro\",\n            },\n          ],\n        },\n        {\n          title: \"Community\",\n          items: [\n            {\n              label: \"German Discord (UsenetDE)\",\n              href: \"https://discord.gg/Z2wTmrmFgn\",\n            },\n            {\n              label: \"TRaSH-Guides\",\n              href: \"https://trash-guides.info/\",\n            },\n            // {\n            //   label: \"Discord\",\n            //   href: \"https://discordapp.com/invite/docusaurus\",\n            // },\n            // {\n            //   label: \"X\",\n            //   href: \"https://x.com/docusaurus\",\n            // },\n          ],\n        },\n        {\n          title: \"More\",\n          items: [\n            // {\n            //   label: \"Blog\",\n            //   to: \"/blog\",\n            // },\n            {\n              label: \"GitHub\",\n              href: \"https://github.com/raydak-labs/configarr\",\n            },\n            {\n              label: \"Stack Overflow\",\n              href: \"https://stackoverflow.com/questions/tagged/configarr\",\n            },\n            {\n              label: \"YouTube\",\n              href: \"https://www.youtube.com/@raydak-labs\",\n            },\n          ],\n        },\n      ],\n      copyright: `Copyright © ${new Date().getFullYear()} Configarr. Built with Docusaurus.`,\n    },\n    prism: {\n      theme: prismThemes.github,\n      darkTheme: prismThemes.dracula,\n    },\n  } satisfies Preset.ThemeConfig,\n\n  // use trailing slashes as github pages uses them, this also fixes the sitemap.xml\n  trailingSlash: true,\n};\n\nexport default config;\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"configarr\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"docusaurus\": \"docusaurus\",\n    \"start\": \"docusaurus start\",\n    \"build\": \"docusaurus build\",\n    \"swizzle\": \"docusaurus swizzle\",\n    \"deploy\": \"docusaurus deploy\",\n    \"clear\": \"docusaurus clear\",\n    \"serve\": \"docusaurus serve\",\n    \"write-translations\": \"docusaurus write-translations\",\n    \"write-heading-ids\": \"docusaurus write-heading-ids\",\n    \"typecheck\": \"tsc\"\n  },\n  \"dependencies\": {\n    \"@docusaurus/core\": \"3.9.2\",\n    \"@docusaurus/preset-classic\": \"3.9.2\",\n    \"@mdx-js/react\": \"3.1.1\",\n    \"clsx\": \"2.1.1\",\n    \"docusaurus-lunr-search\": \"3.6.1\",\n    \"lunr\": \"2.3.9\",\n    \"prism-react-renderer\": \"2.4.1\",\n    \"raw-loader\": \"4.0.2\",\n    \"react\": \"19.2.4\",\n    \"react-dom\": \"19.2.4\"\n  },\n  \"devDependencies\": {\n    \"@docusaurus/module-type-aliases\": \"3.9.2\",\n    \"@docusaurus/tsconfig\": \"3.9.2\",\n    \"@docusaurus/types\": \"3.9.2\",\n    \"typescript\": \"6.0.2\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.5%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 3 chrome version\",\n      \"last 3 firefox version\",\n      \"last 5 safari version\"\n    ]\n  },\n  \"engines\": {\n    \"node\": \"25.9.0\"\n  }\n}\n"
  },
  {
    "path": "docs/sidebars.ts",
    "content": "import type { SidebarsConfig } from \"@docusaurus/plugin-content-docs\";\n\n// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)\n\n/**\n * Creating a sidebar enables you to:\n - create an ordered group of docs\n - render a sidebar for each doc of that group\n - provide next/previous navigation\n\n The sidebars can be generated from the filesystem, or explicitly defined here.\n\n Create as many sidebars as you want.\n */\nconst sidebars: SidebarsConfig = {\n  // By default, Docusaurus generates a sidebar from the docs folder structure\n  tutorialSidebar: [{ type: \"autogenerated\", dirName: \".\" }],\n\n  // But you can create a sidebar manually\n  /*\n  tutorialSidebar: [\n    'intro',\n    'hello',\n    {\n      type: 'category',\n      label: 'Tutorial',\n      items: ['tutorial-basics/create-a-document'],\n    },\n  ],\n   */\n};\n\nexport default sidebars;\n"
  },
  {
    "path": "docs/src/components/HomepageFeatures/index.tsx",
    "content": "import Heading from \"@theme/Heading\";\nimport clsx from \"clsx\";\nimport styles from \"./styles.module.css\";\n\ntype FeatureItem = {\n  title: string;\n  Svg?: React.ComponentType<React.ComponentProps<\"svg\">>;\n  imageUrl?: string;\n  imageAlt?: string;\n  description: JSX.Element;\n};\n\nconst FeatureList: FeatureItem[] = [\n  {\n    title: \"Easy Configuration\",\n    Svg: require(\"@site/static/img/undraw_docusaurus_mountain.svg\").default,\n    description: <>Configure your entire media stack with a single YAML file. Simple, intuitive, and powerful configuration options.</>,\n  },\n  {\n    title: \"Multi-Platform Support\",\n    Svg: require(\"@site/static/img/undraw_docusaurus_tree.svg\").default,\n    description: (\n      <>\n        Run Configarr anywhere with our Docker container or deploy it on Kubernetes. Perfect for both home servers and cloud environments.\n      </>\n    ),\n  },\n  {\n    title: \"Automated Setup\",\n    Svg: require(\"@site/static/img/undraw_docusaurus_react.svg\").default,\n    description: (\n      <>\n        Let Configarr handle the complex setup of your media applications. From Sonarr,Radarr,Lidarr,Readarr, Whisparr: we've got you\n        covered!\n      </>\n    ),\n  },\n  {\n    title: \"TRaSH-Guides Support\",\n    imageUrl: require(\"@site/static/img/trash_logo.webp\").default, //\"https://trash-guides.info/img/logo.png\" ,\n    imageAlt: \"Logo of TRaSH-Guides\",\n    description: (\n      <>\n        Seamlessly integrate TRaSH-Guides into your workflow with robust support for its comprehensive documentation and automation tools.\n        Effortlessly configure QualityProfiles, CustomFormats, and other advanced settings to optimize your media management experience,\n        ensuring precise and efficient handling of your library.\n      </>\n    ),\n  },\n  {\n    title: \"Experimental support\",\n    Svg: require(\"@site/static/img/experiment.svg\").default,\n    description: (\n      <>\n        Explore cutting-edge features with experimental support for popular Arr tools like Readarr, Lidarr, and Whisparr. Stay ahead of the\n        curve by testing innovative functionalities designed to enhance your media automation and management capabilities.\n      </>\n    ),\n  },\n  {\n    title: \"Community Driven\",\n    Svg: require(\"@site/static/img/undraw_docusaurus_react.svg\").default,\n    description: (\n      <>Written for the community and working with the community to provide the best experience for managing your media stack!</>\n    ),\n  },\n];\n\nfunction Feature({ title, Svg, description, imageUrl, imageAlt }: FeatureItem) {\n  return (\n    <div className={clsx(\"col col--4\")}>\n      <div className=\"text--center\">\n        {Svg ? (\n          <Svg className={styles.featureSvg} role=\"img\" />\n        ) : (\n          <img src={imageUrl} className={styles.featureSvg} alt={imageAlt} title={imageAlt} />\n        )}\n      </div>\n      <div className=\"text--center padding-horiz--md\">\n        <Heading as=\"h3\">{title}</Heading>\n        <p>{description}</p>\n      </div>\n    </div>\n  );\n}\n\nexport default function HomepageFeatures(): JSX.Element {\n  return (\n    <section className={styles.features}>\n      <div className=\"container\">\n        <div className=\"row\">\n          {FeatureList.map((props, idx) => (\n            <Feature key={idx} {...props} />\n          ))}\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/HomepageFeatures/styles.module.css",
    "content": ".features {\n  display: flex;\n  align-items: center;\n  padding: 2rem 0;\n  width: 100%;\n}\n\n.featureSvg {\n  height: 200px;\n  width: 200px;\n}\n"
  },
  {
    "path": "docs/src/css/custom.css",
    "content": "/**\n * Any CSS included here will be global. The classic template\n * bundles Infima by default. Infima is a CSS framework designed to\n * work well for content-centric websites.\n */\n\n/* You can override the default Infima variables here. */\n:root {\n  --ifm-color-primary: #8659c4;\n  --ifm-color-primary-dark: #603d8c;\n  --ifm-color-primary-darker: #4e3271;\n  --ifm-color-primary-darkest: #331a51;\n  --ifm-color-primary-light: #9d77d5;\n  --ifm-color-primary-lighter: #b99de3;\n  --ifm-color-primary-lightest: #d3c3ef;\n  --ifm-code-font-size: 95%;\n  --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);\n}\n\n/* --color-studio-50: #f9f7fd;\n--color-studio-100: #f2edfa;\n--color-studio-200: #e6def6;\n--color-studio-300: #d3c3ef;\n--color-studio-400: #b99de3;\n--color-studio-500: #9d77d5;\n--color-studio-600: #8659c4;\n--color-studio-700: #7649b2;\n--color-studio-800: #603d8c;\n--color-studio-900: #4e3271;\n--color-studio-950: #331a51; */\n\n/* For readability concerns, you should choose a lighter palette in dark mode. */\n[data-theme=\"dark\"] {\n  --ifm-color-primary: #9d77d5;\n  --ifm-color-primary-dark: #8659c4;\n  --ifm-color-primary-darker: #7649b2;\n  --ifm-color-primary-darkest: #603d8c;\n  --ifm-color-primary-light: #b99de3;\n  --ifm-color-primary-lighter: #d3c3ef;\n  --ifm-color-primary-lightest: #e6def6;\n  --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);\n}\n\n.configarrHeader {\n  font-size: 18px;\n  font-weight: bolder;\n}\n\n.comparisonTableNote {\n  font-size: 12px;\n}\n\n.youtubeContainer {\n  position: relative;\n  overflow: hidden;\n  width: 100%;\n  padding-top: 56.25%; /* 16:9 Aspect Ratio (divide 9 by 16 = 0.5625) */\n}\n\n.youtubeVideo {\n  position: absolute;\n  top: 0;\n  left: 0;\n  bottom: 0;\n  right: 0;\n  width: 100%;\n  height: 100%;\n}\n\n.navbarGithubStars {\n  height: 20px; /* var(--docusaurus-announcement-bar-height); */\n  width: 90px;\n}\n\n.configarr-badge {\n  background-color: var(--ifm-color-primary);\n  border-color: var(--ifm-color-primary);\n  font-size: 50%;\n}\n\n.table-of-contents .configarr-badge {\n  font-size: 75%;\n  padding: 2px 4px;\n}\n"
  },
  {
    "path": "docs/src/pages/index.module.css",
    "content": "/**\n * CSS files with the .module.css suffix will be treated as CSS modules\n * and scoped locally.\n */\n\n.heroBanner {\n  padding: 4rem 0;\n  text-align: center;\n  position: relative;\n  overflow: hidden;\n}\n\n@media screen and (max-width: 996px) {\n  .heroBanner {\n    padding: 2rem;\n  }\n}\n\n.buttons {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 2rem;\n}\n\n.buttonsGithubStars {\n  display: flex;\n}\n"
  },
  {
    "path": "docs/src/pages/index.tsx",
    "content": "import Link from \"@docusaurus/Link\";\nimport useDocusaurusContext from \"@docusaurus/useDocusaurusContext\";\nimport HomepageFeatures from \"@site/src/components/HomepageFeatures\";\nimport Heading from \"@theme/Heading\";\nimport Layout from \"@theme/Layout\";\nimport clsx from \"clsx\";\n\nimport Head from \"@docusaurus/Head\";\nimport styles from \"./index.module.css\";\nimport useBaseUrl from \"@docusaurus/useBaseUrl\";\n\nfunction HomepageHeader() {\n  const { siteConfig } = useDocusaurusContext();\n  return (\n    <header className={clsx(\"hero hero--primary\", styles.heroBanner)}>\n      <div className=\"container\">\n        <img src={useBaseUrl(\"img/logo.svg\")} alt=\"Configarr Logo\" width={150} title=\"Configarr logo\" />\n        <Heading as=\"h1\" className=\"hero__title\">\n          {siteConfig.title}\n        </Heading>\n        <p className=\"hero__subtitle\">{siteConfig.tagline}</p>\n        <div className={styles.buttons}>\n          <Link className=\"button button--secondary button--lg\" to=\"/docs/intro\">\n            Get Started !\n          </Link>\n\n          <span className={styles.buttonsGithubStars}>\n            <iframe\n              src=\"https://ghbtns.com/github-btn.html?user=raydak-labs&amp;repo=configarr&amp;type=star&amp;count=true&amp;size=large\"\n              width={160}\n              height={30}\n              title=\"GitHub Stars\"\n            />\n          </span>\n        </div>\n      </div>\n    </header>\n  );\n}\n\nexport default function Home(): JSX.Element {\n  const { siteConfig } = useDocusaurusContext();\n\n  return (\n    <Layout>\n      <Head>\n        <title>Configarr - Configuration Management Simplified</title>\n      </Head>\n      <HomepageHeader />\n      <main>\n        <HomepageFeatures />\n      </main>\n    </Layout>\n  );\n}\n"
  },
  {
    "path": "docs/src/pages/markdown-page.md",
    "content": "---\ntitle: Markdown page example\n---\n\n# Markdown page example\n\nYou don't need React to write simple standalone pages.\n"
  },
  {
    "path": "docs/static/.nojekyll",
    "content": ""
  },
  {
    "path": "docs/static/robots.txt",
    "content": "User-agent: *\nDisallow:\nSitemap: https://configarr.de/sitemap.xml\n"
  },
  {
    "path": "docs/tsconfig.json",
    "content": "{\n  // This file is not used in compilation. It is here just for a nice editor experience.\n  \"extends\": \"@docusaurus/tsconfig\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\"\n  }\n}\n"
  },
  {
    "path": "esbuild.ts",
    "content": "import esbuild from \"esbuild\";\n\nconst externalizedModules = {};\n\n// Get build-time constants from environment variables (set by Docker build args)\nconst buildTime = process.env.BUILD_TIME ?? \"\";\nconst githubRunId = process.env.GITHUB_RUN_ID ?? \"\";\nconst githubRepo = process.env.GITHUB_REPO ?? \"\";\nconst githubSha = process.env.GITHUB_SHA ?? \"\";\nconst configarrVersion = process.env.CONFIGARR_VERSION ?? \"dev\";\n\nawait esbuild.build({\n  //inject: [\"cjs-shim.ts\"],\n  entryPoints: [\"./src/index.ts\"],\n  bundle: true,\n  sourcemap: \"inline\",\n  platform: \"node\",\n  target: \"node20\",\n  //external: [\"fs\", \"child_process\", \"crypto\", \"os\", \"path\"],\n  //plugins: [externalNativeModulesPlugin(externalizedModules)],\n\n  format: \"cjs\",\n  outfile: \"bundle.cjs\",\n\n  // Inject build-time constants using define\n  define: {\n    \"process.env.BUILD_TIME\": JSON.stringify(buildTime),\n    \"process.env.GITHUB_RUN_ID\": JSON.stringify(githubRunId),\n    \"process.env.GITHUB_REPO\": JSON.stringify(githubRepo),\n    \"process.env.GITHUB_SHA\": JSON.stringify(githubSha),\n    \"process.env.CONFIGARR_VERSION\": JSON.stringify(configarrVersion),\n  },\n\n  //format: \"esm\",\n  //outfile: \"out2.mjs\",\n});\n"
  },
  {
    "path": "examples/full/.gitignore",
    "content": "!config/*.yml\ndockerrepos/\ndata/\n"
  },
  {
    "path": "examples/full/README.md",
    "content": "# Configarr - Full example\n\nThis example contains every feature provided by configarr.\n\n1. Start arr containers with `docker-compose up -d`\n   - Create network for containers\n   - Creates sonarr instance\n   - Creates radarr instance\n   - API keys are provided with the `xml` configs\n2. Run configarr with `docker-compose -f docker-compose.jobs.yml run --rm configarr`\n\nURLs:\n\n- sonarr: http://localhost:6500\n- radarr: http://localhost:6501\n\nCleanup: `docker-compose down -v`\n\n## Development\n\nYou can also use this full example template as testing environment by utilizing the `docker-compose.local.yml` file.\n\n- Build image: `docker-compose -f docker-compose.local.yml build`\n- Run image: `docker-compose -f docker-compose.local.yml run --rm configarr`\n  - This will run always with the current code changes; no need to rebuild for simple code changes because the code will be mounted into the container\n"
  },
  {
    "path": "examples/full/cfs/custom-size-bigger-40gb.json",
    "content": "{\n  \"trash_id\": \"custom-size-more-40gb\",\n  \"trash_scores\": {\n    \"default\": -10000\n  },\n  \"trash_description\": \"Size: Block sizes over 40GB\",\n  \"custom_inf\": \"Does not work because SizeSpecification is not supported by recyclarr\",\n  \"name\": \"Size: Block More 40GB\",\n  \"includeCustomFormatWhenRenaming\": false,\n  \"specifications\": [\n    {\n      \"name\": \"Size\",\n      \"implementation\": \"SizeSpecification\",\n      \"negate\": false,\n      \"required\": true,\n      \"fields\": {\n        \"min\": 1,\n        \"max\": 9\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "examples/full/docker-compose.jobs.yml",
    "content": "services:\n  configarr:\n    image: ghcr.io/raydak-labs/configarr:latest\n    networks:\n      - configarr-full\n    volumes:\n      - ./config:/app/config\n      - ./dockerrepos:/app/repos\n      - ./cfs:/app/cfs\n      - ./templates:/app/templates\n\nnetworks:\n  configarr-full:\n    name: configarr-full\n    external: true\n"
  },
  {
    "path": "examples/full/docker-compose.local.yml",
    "content": "services:\n  configarr:\n    #image: ghcr.io/raydak-labs/configarr:1.2.1\n    image: configarr:local\n    build:\n      context: ../..\n      dockerfile: Dockerfile\n      target: dev\n    networks:\n      - configarr-full\n    volumes:\n      - ./config:/app/config\n      - ./dockerrepos:/app/repos\n      - ./cfs:/app/cfs\n      - ./templates:/app/templates\n      - ../../src:/app/src\n      - ./debug:/app/debug\n      - ./data:/data\n    environment:\n      LOG_LEVEL: debug\n      DEBUG_CREATE_FILES: false\n      # If you want to map all data to new path instead of /app\n      #ROOT_PATH: /data\n\nnetworks:\n  configarr-full:\n    name: configarr-full\n    external: true\n"
  },
  {
    "path": "examples/full/docker-compose.yml",
    "content": "services:\n  sonarr:\n    image: lscr.io/linuxserver/sonarr:4.0.17\n    networks:\n      - configarr-full\n    environment:\n      - PUID=1000\n      - PGID=1000\n      - TZ=Etc/UTC\n    volumes:\n      - sonarr:/config\n      - ${PWD}/sonarr.xml:/config/config.xml:rw\n    ports:\n      - 6500:8989\n    restart: unless-stopped\n\n  radarr:\n    image: lscr.io/linuxserver/radarr:6.0.4\n    networks:\n      - configarr-full\n    environment:\n      - PUID=1000\n      - PGID=1000\n      - TZ=Etc/UTC\n    volumes:\n      - radarr:/config\n      - radarr-download1:/downloads/1\n      - radarr-download2:/downloads/2\n      - ${PWD}/radarr.xml:/config/config.xml:rw\n    ports:\n      - 6501:7878\n    restart: unless-stopped\n\n  # experimental\n  whisparr:\n    image: ghcr.io/hotio/whisparr:v3-3.0.0.695\n    networks:\n      - configarr-full\n    ports:\n      - \"6502:6969\"\n    environment:\n      - PUID=1000\n      - PGID=1000\n      - UMASK=002\n      - TZ=Etc/UTC\n    volumes:\n      - whisparr:/config\n      - ${PWD}/whisparr.xml:/config/config.xml:rw\n    restart: unless-stopped\n\n  # experimental\n  readarr:\n    image: lscr.io/linuxserver/readarr:develop-0.4.3.2665-ls130\n    networks:\n      - configarr-full\n    environment:\n      - PUID=1000\n      - PGID=1000\n      - TZ=Etc/UTC\n    volumes:\n      - readarr:/config\n      - ${PWD}/readarr.xml:/config/config.xml:rw\n    ports:\n      - 6503:8787\n    restart: unless-stopped\n\n  # experimental\n  lidarr:\n    image: lscr.io/linuxserver/lidarr:2.14.5\n    networks:\n      - configarr-full\n    environment:\n      - PUID=1000\n      - PGID=1000\n      - TZ=Etc/UTC\n    volumes:\n      - lidarr:/config\n      - ${PWD}/lidarr.xml:/config/config.xml:rw\n    ports:\n      - 6504:8686\n    restart: unless-stopped\n\nnetworks:\n  configarr-full:\n    name: configarr-full\n\nvolumes:\n  sonarr:\n  radarr:\n  whisparr:\n  readarr:\n  lidarr:\n  radarr-download1:\n  radarr-download2:\n"
  },
  {
    "path": "examples/full/lidarr.xml",
    "content": "<Config>\n  <BindAddress>*</BindAddress>\n  <Port>8686</Port>\n  <SslPort>6868</SslPort>\n  <EnableSsl>False</EnableSsl>\n  <LaunchBrowser>True</LaunchBrowser>\n  <ApiKey>713067b7319045df9c7fa6847d8ce717</ApiKey>\n  <AuthenticationMethod>Basic</AuthenticationMethod>\n  <AuthenticationRequired>DisabledForLocalAddresses</AuthenticationRequired>\n  <Branch>master</Branch>\n  <LogLevel>info</LogLevel>\n  <SslCertPath></SslCertPath>\n  <SslCertPassword></SslCertPassword>\n  <UrlBase></UrlBase>\n  <InstanceName>Lidarr</InstanceName>\n  <UpdateMechanism>Docker</UpdateMechanism>\n</Config>"
  },
  {
    "path": "examples/full/radarr.xml",
    "content": "<Config>\n  <BindAddress>*</BindAddress>\n  <Port>7878</Port>\n  <SslPort>9898</SslPort>\n  <EnableSsl>False</EnableSsl>\n  <LaunchBrowser>True</LaunchBrowser>\n  <ApiKey>0daa3a2b940f4e08bac991e9a30e9e12</ApiKey>\n  <AuthenticationMethod>forms</AuthenticationMethod>\n  <AuthenticationRequired>DisabledForLocalAddresses</AuthenticationRequired>\n  <Branch>master</Branch>\n  <LogLevel>info</LogLevel>\n  <SslCertPath></SslCertPath>\n  <SslCertPassword></SslCertPassword>\n  <UrlBase></UrlBase>\n  <InstanceName>Radarr</InstanceName>\n  <UpdateMechanism>Docker</UpdateMechanism>\n</Config>"
  },
  {
    "path": "examples/full/readarr.xml",
    "content": "<Config>\n  <BindAddress>*</BindAddress>\n  <Port>8787</Port>\n  <SslPort>6868</SslPort>\n  <EnableSsl>False</EnableSsl>\n  <LaunchBrowser>True</LaunchBrowser>\n  <ApiKey>e001718ba38a424d8aae7cb391b5d27a</ApiKey>\n  <AuthenticationMethod>Basic</AuthenticationMethod>\n  <AuthenticationRequired>DisabledForLocalAddresses</AuthenticationRequired>\n  <Branch>develop</Branch>\n  <LogLevel>debug</LogLevel>\n  <SslCertPath></SslCertPath>\n  <SslCertPassword></SslCertPassword>\n  <UrlBase></UrlBase>\n  <InstanceName>Readarr</InstanceName>\n  <UpdateMechanism>Docker</UpdateMechanism>\n</Config>"
  },
  {
    "path": "examples/full/sonarr.xml",
    "content": "<Config>\n  <BindAddress>*</BindAddress>\n  <Port>8989</Port>\n  <SslPort>9898</SslPort>\n  <EnableSsl>False</EnableSsl>\n  <LaunchBrowser>True</LaunchBrowser>\n  <ApiKey>5e792b7e0fe14f58b8e92bf0902d4a44</ApiKey>\n  <AuthenticationMethod>Basic</AuthenticationMethod>\n  <AuthenticationRequired>DisabledForLocalAddresses</AuthenticationRequired>\n  <Branch>main</Branch>\n  <LogLevel>info</LogLevel>\n  <SslCertPath></SslCertPath>\n  <SslCertPassword></SslCertPassword>\n  <UrlBase></UrlBase>\n  <InstanceName>Sonarr</InstanceName>\n  <UpdateMechanism>Docker</UpdateMechanism>\n</Config>"
  },
  {
    "path": "examples/full/templates/lidarr.yml",
    "content": "quality_profiles:\n  - name: ExampleProfile\n    reset_unmatched_scores:\n      enabled: true\n    upgrade:\n      allowed: true\n      until_quality: FLAC\n      until_score: 10\n    min_format_score: 1\n    quality_sort: top\n    qualities:\n      - name: FLAC\n      - name: \"MP3-96\"\n"
  },
  {
    "path": "examples/full/templates/radarr-cf.yml",
    "content": "custom_formats:\n  - trash_ids:\n      # Can be copied from recyclarr\n\n      # HQ Release Groups\n      - 3a3ff47579026e76d6504ebea39390de # Remux Tier 01\n      - 9f98181fe5a3fbeb0cc29340da2a468a # Remux Tier 02\n      - 8baaf0b3142bf4d94c42a724f034e27a # Remux Tier 03\n      - c20f169ef63c5f40c2def54abaf4438e # WEB Tier 01\n      - 403816d65392c79236dcb6dd591aeda4 # WEB Tier 02\n      - af94e0fe497124d1f9ce732069ec8c3b # WEB Tier 03\n    assign_scores_to:\n      - name: ExampleProfile\n\n  - trash_ids:\n      # Streaming Services\n      - b3b3a6ac74ecbd56bcdbefa4799fb9df # AMZN\n      - 40e9380490e748672c2522eaaeb692f7 # ATVP\n      - f6ff65b3f4b464a79dcc75950fe20382 # CRAV\n      - 84272245b2988854bfb76a16e60baea5 # DSNP\n      - 917d1f2c845b2b466036b0cc2d7c72a3 # FOD\n      - 509e5f41146e278f9eab1ddaceb34515 # HBO\n      - 5763d1b0ce84aff3b21038eea8e9b8ad # HMAX\n      - 526d445d4c16214309f0fd2b3be18a89 # Hulu\n      - 6185878161f1e2eef9cd0641a0d09eae # iP\n      - 6a061313d22e51e0f25b7cd4dc065233 # MAX\n      - 170b1d363bd8516fbf3a3eb05d4faff6 # NF\n      - fbca986396c5e695ef7b2def3c755d01 # OViD\n      - bf7e73dd1d85b12cc527dc619761c840 # Pathe\n      - c9fd353f8f5f1baf56dc601c4cb29920 # PCOK\n      - e36a0ba1bc902b26ee40818a1d59b8bd # PMTP\n      - c2863d2a50c9acad1fb50e53ece60817 # STAN\n      - f1b0bae9bc222dab32c1b38b5a7a1088 # TVer\n      - 279bda7434fd9075786de274e6c3c202 # U-NEXT\n    assign_scores_to:\n      - name: ExampleProfile\n\n  - trash_ids:\n      - custom-size-more-40gb # custom CF\n    assign_scores_to:\n      - name: ExampleProfile\n        score: -10000\n"
  },
  {
    "path": "examples/full/templates/radarr-quality.yml",
    "content": "quality_profiles:\n  - name: ExampleProfile\n    reset_unmatched_scores:\n      enabled: true\n    upgrade:\n      allowed: true\n      until_quality: WEB 2160p\n      until_score: 1000\n    min_format_score: 0\n    quality_sort: top\n    qualities:\n      - name: Remux-2160p\n      - name: WEB 2160p\n        qualities:\n          - WEBDL-2160p\n          - WEBRip-2160p\n      - name: Remux-1080p\n      - name: WEB 1080p\n        qualities:\n          - WEBDL-1080p\n          - WEBRip-1080p\n"
  },
  {
    "path": "examples/full/templates/readarr.yml",
    "content": "quality_profiles:\n  - name: ExampleProfile\n    reset_unmatched_scores:\n      enabled: true\n    upgrade:\n      allowed: true\n      until_quality: MOBI\n      until_score: 1000\n    min_format_score: 5\n    quality_sort: top\n    qualities:\n      - name: EPUB\n      - name: MOBI\n"
  },
  {
    "path": "examples/full/templates/sonarr-cf.yml",
    "content": "custom_formats:\n  - trash_ids:\n      # Can be copied from recyclarr\n\n      # HQ Source Groups\n      - e6258996055b9fbab7e9cb2f75819294 # WEB Tier 01\n      - 58790d4e2fdcd9733aa7ae68ba2bb503 # WEB Tier 02\n      - d84935abd3f8556dcd51d4f27e22d0a6 # WEB Tier 03\n      - d0c516558625b04b363fa6c5c2c7cfd4 # WEB Scene\n    assign_scores_to:\n      - name: ExampleProfile\n\n  - trash_ids:\n      - custom-size-more-40gb # custom language\n    assign_scores_to:\n      - name: ExampleProfile\n        score: -10000\n\ncustomFormatDefinitions:\n  - trash_id: sonarr-cf\n    trash_scores:\n      default: -10000\n    trash_description: \"Language: German Only 2\"\n    name: \"Language: Not German 2\"\n    includeCustomFormatWhenRenaming: false\n    specifications:\n      - name: Not German Language\n        implementation: LanguageSpecification\n        negate: true\n        required: false\n        fields:\n          value: 4\n"
  },
  {
    "path": "examples/full/templates/sonarr-quality.yml",
    "content": "quality_profiles:\n  - name: ExampleProfile\n    reset_unmatched_scores:\n      enabled: true\n    upgrade:\n      allowed: true\n      until_quality: WEB 2160p\n      until_score: 1000\n    min_format_score: 0\n    quality_sort: top\n    qualities:\n      - name: WEB 2160p\n        qualities:\n          - WEBDL-2160p\n          - WEBRip-2160p\n      - name: WEB 1080p\n        qualities:\n          - WEBDL-1080p\n          - WEBRip-1080p\n"
  },
  {
    "path": "examples/full/templates/whisparr.yml",
    "content": "quality_profiles:\n  - name: ExampleProfile\n    reset_unmatched_scores:\n      enabled: true\n    upgrade:\n      allowed: true\n      until_quality: WEB 2160p\n      until_score: 1000\n    min_format_score: 5\n    quality_sort: top\n    qualities:\n      - name: VR\n      - name: Remux-2160p\n      - name: WEB 2160p\n        qualities:\n          - WEBDL-2160p\n          - WEBRip-2160p\n      - name: Remux-1080p\n      - name: WEB 1080p\n        qualities:\n          - WEBDL-1080p\n          - WEBRip-1080p\n"
  },
  {
    "path": "examples/full/whisparr.xml",
    "content": "<Config>\n  <BindAddress>*</BindAddress>\n  <Port>6969</Port>\n  <SslPort>9898</SslPort>\n  <EnableSsl>False</EnableSsl>\n  <LaunchBrowser>True</LaunchBrowser>\n  <ApiKey>2ebd18db81e14c2d98d06ef4b865aaa8</ApiKey>\n  <AuthenticationMethod>Basic</AuthenticationMethod>\n  <AuthenticationRequired>DisabledForLocalAddresses</AuthenticationRequired>\n  <Branch>eros</Branch>\n  <LogLevel>info</LogLevel>\n  <SslCertPath></SslCertPath>\n  <SslCertPassword></SslCertPassword>\n  <UrlBase></UrlBase>\n  <InstanceName>Whisparr</InstanceName>\n  <UpdateMechanism>Docker</UpdateMechanism>\n</Config>"
  },
  {
    "path": "examples/scheduled/.gitignore",
    "content": "!config/*.yml\ndockerrepos/\n"
  },
  {
    "path": "examples/scheduled/README.md",
    "content": "# Configarr - scheduled exammple\n\nThis is an example how you could run configarr in a scheduled manner outside of kubernetes.\nKubernetes has built in support with CronJobs.\n\nURLs:\n\n- radarr: http://localhost:6501\n\nCleanup:\n\n```bash\ndocker-compose -f docker-compose.ofelia.yml down -v\ndocker-compose -f docker-compose.cron.yml -p cron down -v\ndocker-compose down -v\n```\n\n## Variant: Ofelia (recommended)\n\nCompose file: `docker-compose.ofelia.yml`\n\nThis is run with the `ofelia` image (https://github.com/mcuadros/ofelia).\nCheck guide of ofelia for more configs.\nWe can rerun an existing container and reuse it or create new containers.\nCheck `ofelia.ini` for example.\n\nThe example shows two variants.\nPlease just use one which matches your needs.\nBoth solutions work:\n\n- running with an existing container which always exits and will be restarted\n- always running a fresh new container.\n\n```bash\n# full path is needed in multiple docker-compose files. Either set direct in file or via env variable\nexport CONFIGARR_FULL_PATH=$(pwd)\ndocker-compose up -d\n\n# ofelia\n# Please update paths in ofelia.ini before running\ndocker-compose -f docker-compose.ofelia.yml -p ofelia up -d\ndocker-compose -f docker-compose.ofelia.yml -p ofelia logs\n# clean\ndocker-compose -f docker-compose.ofelia.yml -p ofelia down -v\n```\n\n## Variant: Cron-Like\n\nCompose file: `docker-compose.cron.yml`\n\nThis starts a container (https://github.com/BlackDark/dockerfiles/tree/main/cron-dind) which will run cron and we have to mount cron like configurations.\nCheck the compose file and mounted volumes for how this works.\nIn summary: mount folder with cron files, cron triggers commands defined there.\n\nThe example shows two variants.\nPlease just use one which matches your needs.\nBoth solutions work:\n\n- running with an existing container which always exits and will be restarted\n- always running a fresh new container.\n\n```bash\n# full path is needed in multiple docker-compose files. Either set direct in file or via env variable\nexport CONFIGARR_FULL_PATH=$(pwd)\ndocker-compose up -d\n\n# cron like\n# please update the paths and uncomment in dir ./cron/*\ndocker-compose -f docker-compose.cron.yml -p cron up -d cron\n\n# Optional: If using the container reuse functionality:\ndocker-compose -f docker-compose.cron.yml -p cron up -d configarr\n\ndocker-compose -f docker-compose.cron.yml -p cron logs\n\n# clean\ndocker-compose -f docker-compose.cron.yml -p cron down -v\n```\n\n## Side notes\n\nWith approaches like this you can easily extends functionalities like:\n\n- notifications (errored state or not)\n- cleanup procedures\n\n## Drawbacks with some solutions\n\n- because we are reusing the `docker.sock` (this means running containers on the host) we have to use absolute paths for mounts.\n"
  },
  {
    "path": "examples/scheduled/cron/configarr-reuse",
    "content": "LOGFILE_DEFAULT=/var/log/cron.log\n#CONFIGARR_FULL_PATH=/tmp/configarr/full/path\n\n* * * * * root docker compose -f /app/docker-compose.yaml -p cron start configarr >> $LOGFILE_DEFAULT 2>&1\n"
  },
  {
    "path": "examples/scheduled/cron/configarr-run",
    "content": "LOGFILE_DEFAULT=/var/log/cron.log\n#CONFIGARR_FULL_PATH=/tmp/configarr/full/path\n\n* * * * * root docker compose -f /app/docker-compose.yaml -p cron run --rm configarr-run >> $LOGFILE_DEFAULT 2>&1\n"
  },
  {
    "path": "examples/scheduled/docker-compose.cron.yml",
    "content": "services:\n  configarr:\n    image: ghcr.io/raydak-labs/configarr:latest\n    networks:\n      - configarr-full\n    volumes:\n      - ${CONFIGARR_FULL_PATH:?\"missing\"}/config:/app/config\n      - ${CONFIGARR_FULL_PATH:?\"missing\"}/dockerrepos:/app/repos\n      #- ${CONFIGARR_FULL_PATH:?\"missing\"}/cfs:/app/cfs\n      #- ${CONFIGARR_FULL_PATH:?\"missing\"}/templates:/app/templates\n\n  configarr-run:\n    image: ghcr.io/raydak-labs/configarr:latest\n    networks:\n      - configarr-full\n    volumes:\n      - ${CONFIGARR_FULL_PATH:?\"missing\"}/config:/app/config\n      - ${CONFIGARR_FULL_PATH:?\"missing\"}/dockerrepos:/app/repos\n      #- ${CONFIGARR_FULL_PATH:?\"missing\"}/cfs:/app/cfs\n      #- ${CONFIGARR_FULL_PATH:?\"missing\"}/templates:/app/templates\n\n  cron:\n    image: blackdark93/dockerfiles-cron-dind:main\n    volumes:\n      - /var/run/docker.sock:/var/run/docker.sock:ro\n      - ${CONFIGARR_FULL_PATH:?\"missing\"}/cron:/app/schedules:ro\n      - ${CONFIGARR_FULL_PATH:?\"missing\"}/docker-compose.cron.yml:/app/docker-compose.yaml:ro\n\nnetworks:\n  configarr-full:\n    name: configarr-full\n    external: true\n"
  },
  {
    "path": "examples/scheduled/docker-compose.ofelia.yml",
    "content": "services:\n  configarr:\n    container_name: configarr-reused\n    image: ghcr.io/raydak-labs/configarr:latest\n    networks:\n      - configarr-full\n    volumes:\n      - ./config:/app/config\n      - ./dockerrepos:/app/repos\n      #- ./cfs:/app/cfs\n      #- ./templates:/app/templates\n\n  ofelia:\n    image: mcuadros/ofelia:latest\n    command: daemon --config=/opt/config.ini\n    #command: daemon --docker\n    volumes:\n      - /var/run/docker.sock:/var/run/docker.sock:ro\n      - ./ofelia.ini:/opt/config.ini\n\nnetworks:\n  configarr-full:\n    name: configarr-full\n    external: true\n"
  },
  {
    "path": "examples/scheduled/docker-compose.yml",
    "content": "services:\n  radarr:\n    image: lscr.io/linuxserver/radarr:6.0.4\n    networks:\n      - configarr-full\n    environment:\n      - PUID=1000\n      - PGID=1000\n      - TZ=Etc/UTC\n    volumes:\n      - radarr:/config\n      - ${PWD}/radarr.xml:/config/config.xml:rw\n    ports:\n      - 6501:7878\n    restart: unless-stopped\n\nnetworks:\n  configarr-full:\n    name: configarr-full\n\nvolumes:\n  radarr:\n"
  },
  {
    "path": "examples/scheduled/ofelia.ini",
    "content": "; Example of how to run on existing container which will be restarted \n[job-run \"run-configarr-existing-container\"]\nschedule = @every 10s\ncontainer = configarr-reused\n\n; Creates new container and executes with given parameters\n[job-run \"run-configarr-new-container\"]\nschedule = @every 10s\nimage = ghcr.io/raydak-labs/configarr:latest\n; Full path for volume\nvolume = /tmp/configarr/full/path/config:/app/config\nvolume = /tmp/configarr/full/path/dockerrepos:/app/repos\n;volume = /tmp/configarr/full/path/cfs:/app/cfs\n;volume = /tmp/configarr/full/path/templates:/app/templates\nnetwork = configarr-full\n"
  },
  {
    "path": "examples/scheduled/radarr.xml",
    "content": "<Config>\n  <BindAddress>*</BindAddress>\n  <Port>7878</Port>\n  <SslPort>9898</SslPort>\n  <EnableSsl>False</EnableSsl>\n  <LaunchBrowser>True</LaunchBrowser>\n  <ApiKey>0daa3a2b940f4e08bac991e9a30e9e12</ApiKey>\n  <AuthenticationMethod>Basic</AuthenticationMethod>\n  <AuthenticationRequired>DisabledForLocalAddresses</AuthenticationRequired>\n  <Branch>master</Branch>\n  <LogLevel>info</LogLevel>\n  <SslCertPath></SslCertPath>\n  <SslCertPassword></SslCertPassword>\n  <UrlBase></UrlBase>\n  <InstanceName>Radarr</InstanceName>\n  <UpdateMechanism>Docker</UpdateMechanism>\n</Config>"
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"Configarr flake\";\n\n  inputs = {\n    flake-parts.url = \"github:hercules-ci/flake-parts\";\n    nixpkgs.url = \"github:NixOS/nixpkgs/nixpkgs-unstable\";\n    systems.url = \"github:nix-systems/default\";\n  };\n\n  outputs = {\n    flake-parts,\n    nixpkgs,\n    ...\n  } @ inputs:\n    flake-parts.lib.mkFlake {inherit inputs;} {\n      systems = [\n        \"x86_64-linux\"\n        \"aarch64-linux\"\n        \"x86_64-darwin\"\n        \"aarch64-darwin\"\n      ];\n\n      flake = {\n        nixosModules.default = ./pkgs/nix/module;\n      };\n\n      perSystem = {system, ...}: let\n        pkgs = nixpkgs.legacyPackages.${system};\n      in {\n        packages.default = import ./pkgs/nix/package.nix {\n          inherit pkgs;\n          inherit (pkgs) lib;\n        };\n      };\n    };\n}\n"
  },
  {
    "path": "generate-api.ts",
    "content": "import { unlinkSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { generateApi } from \"swagger-typescript-api\";\n\nconst PATH_TO_OUTPUT_DIR = path.resolve(process.cwd(), \"./src/__generated__\");\nconst PATH_SONARR_DIR = path.resolve(PATH_TO_OUTPUT_DIR, \"sonarr\");\nconst PATH_RADARR_DIR = path.resolve(PATH_TO_OUTPUT_DIR, \"radarr\");\nconst PATH_WHISPARR_DIR = path.resolve(PATH_TO_OUTPUT_DIR, \"whisparr\");\nconst PATH_READARR_DIR = path.resolve(PATH_TO_OUTPUT_DIR, \"readarr\");\nconst PATH_LIDARR_DIR = path.resolve(PATH_TO_OUTPUT_DIR, \"lidarr\");\n\nconst main = async () => {\n  await generateApi({\n    output: PATH_SONARR_DIR,\n    url: \"https://raw.githubusercontent.com/Sonarr/Sonarr/develop/src/Sonarr.Api.V3/openapi.json\",\n    modular: true,\n    singleHttpClient: true,\n    // @ts-ignore little hack to have one single client (we are deleting the weird created file for the http-client)\n    fileNames: {\n      httpClient: \"../../ky-client\",\n    },\n  });\n\n  await generateApi({\n    output: PATH_RADARR_DIR,\n    url: \"https://raw.githubusercontent.com/Radarr/Radarr/develop/src/Radarr.Api.V3/openapi.json\",\n    modular: true,\n    singleHttpClient: true,\n    // @ts-ignore little hack to have one single client (we are deleting the weird created file for the http-client)\n    fileNames: {\n      httpClient: \"../../ky-client\",\n    },\n  });\n\n  await generateApi({\n    output: PATH_WHISPARR_DIR,\n    url: \"https://raw.githubusercontent.com/Whisparr/Whisparr/develop/src/Whisparr.Api.V3/openapi.json\",\n    modular: true,\n    singleHttpClient: true,\n    // @ts-ignore little hack to have one single client (we are deleting the weird created file for the http-client)\n    fileNames: {\n      httpClient: \"../../ky-client\",\n    },\n  });\n\n  await generateApi({\n    output: PATH_READARR_DIR,\n    url: \"https://raw.githubusercontent.com/Readarr/Readarr/develop/src/Readarr.Api.V1/openapi.json\",\n    modular: true,\n    singleHttpClient: true,\n    // @ts-ignore little hack to have one single client (we are deleting the weird created file for the http-client)\n    fileNames: {\n      httpClient: \"../../ky-client\",\n    },\n  });\n\n  await generateApi({\n    output: PATH_LIDARR_DIR,\n    url: \"https://raw.githubusercontent.com/lidarr/Lidarr/develop/src/Lidarr.Api.V1/openapi.json\",\n    modular: true,\n    singleHttpClient: true,\n    // @ts-ignore little hack to have one single client (we are deleting the weird created file for the http-client)\n    fileNames: {\n      httpClient: \"../../ky-client\",\n    },\n  });\n\n  unlinkSync(path.resolve(PATH_TO_OUTPUT_DIR, \"..ts\"));\n};\n\nmain();\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"configarr\",\n  \"version\": \"0.0.0\",\n  \"description\": \"\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/raydak-labs/configarr.git\"\n  },\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"tsx esbuild.ts\",\n    \"coverage\": \"vitest run --coverage\",\n    \"generateApi\": \"tsx generate-api.ts && prettier src/__generated__ --write\",\n    \"lint\": \"prettier --log-level error --check .\",\n    \"lint:fix\": \"prettier --list-different --write --log-level error .\",\n    \"start\": \"tsx src/index.ts\",\n    \"test\": \"vitest run\",\n    \"test:watch\": \"vitest\",\n    \"typecheck\": \"tsc --noEmit\",\n    \"release\": \"release-it\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"dotenv\": \"17.3.1\",\n    \"fast-glob\": \"3.3.3\",\n    \"ky\": \"1.14.3\",\n    \"pino\": \"10.3.1\",\n    \"pino-pretty\": \"13.1.3\",\n    \"simple-git\": \"3.33.0\",\n    \"tsx\": \"4.21.0\",\n    \"yaml\": \"2.8.3\",\n    \"zod\": \"4.3.6\"\n  },\n  \"devDependencies\": {\n    \"@hyrious/esbuild-plugin-commonjs\": \"0.2.6\",\n    \"@playwright/test\": \"1.58.2\",\n    \"@release-it/conventional-changelog\": \"10.0.6\",\n    \"@types/node\": \"24.12.0\",\n    \"@vitest/coverage-v8\": \"4.1.2\",\n    \"esbuild\": \"0.27.4\",\n    \"prettier\": \"3.8.1\",\n    \"release-it\": \"19.2.4\",\n    \"swagger-typescript-api\": \"13.6.5\",\n    \"typescript\": \"6.0.2\",\n    \"vitest\": \"4.1.2\"\n  }\n}\n"
  },
  {
    "path": "pkgs/nix/module/config.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}: let\n  cfg = config.services.configarr;\nin {\n  config = lib.mkIf cfg.enable {\n    systemd = {\n      services.configarr = {\n        after = [\n          \"network-online.target\"\n          \"systemd-tmpfiles-setup.service\"\n        ];\n        description = \"Run Configarr (packaged) once\";\n        path = [pkgs.git];\n        preStart = let\n          configFile = pkgs.writeText \"configarr-config.yml\" cfg.config;\n        in ''\n          echo \"Creating configarr config file at ${cfg.dataDir}/config/\"\n          install -D -m 0644 ${configFile} ${cfg.dataDir}/config/config.yml\n          chown ${cfg.user}:${cfg.group} ${cfg.dataDir}/config/config.yml\n          echo \"Created configarr config file at ${cfg.dataDir}/config/\"\n        '';\n        serviceConfig = {\n          EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;\n          ExecStart = let\n            pkg =\n              if (cfg.package != null)\n              then cfg.package\n              else (import ../package.nix {inherit lib pkgs;});\n          in\n            lib.getExe pkg;\n          Group = cfg.group;\n          Type = \"oneshot\";\n          User = cfg.user;\n          WorkingDirectory = cfg.dataDir;\n        };\n        wants = [\"network-online.target\"];\n      };\n\n      timers.configarr = {\n        description = \"Schedule Configarr run\";\n        partOf = [\"configarr.service\"];\n        timerConfig = {\n          OnCalendar = cfg.schedule;\n          Persistent = true;\n          RandomizedDelaySec = \"5m\";\n        };\n        wantedBy = [\"timers.target\"];\n      };\n\n      tmpfiles.rules = [\n        \"d ${cfg.dataDir} 0755 ${cfg.user} ${cfg.group} -\"\n        \"d ${cfg.dataDir}/config 0755 ${cfg.user} ${cfg.group} -\"\n      ];\n    };\n\n    users = {\n      groups = lib.mkIf (cfg.group == \"configarr\") {\n        ${cfg.group} = {};\n      };\n\n      users = lib.mkIf (cfg.user == \"configarr\") {\n        configarr = {\n          description = \"configarr user\";\n          inherit (cfg) group;\n          home = cfg.dataDir;\n          isSystemUser = true;\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "pkgs/nix/module/default.nix",
    "content": "{\n  imports = [\n    ./config.nix\n    ./options.nix\n  ];\n}\n"
  },
  {
    "path": "pkgs/nix/module/options.nix",
    "content": "{\n  lib,\n  pkgs,\n  ...\n}: {\n  options.services.configarr = {\n    config = lib.mkOption {\n      default = \"\";\n      description = \"YAML configuration.\";\n      type = lib.types.str;\n    };\n\n    dataDir = lib.mkOption {\n      default = \"/var/lib/configarr\";\n      description = \"Working directory for Configarr (repos/, config/, etc.).\";\n      type = lib.types.path;\n    };\n\n    enable = lib.mkEnableOption \"Configarr synchronization service\";\n\n    environmentFile = lib.mkOption {\n      default = null;\n      description = ''\n        Environment file as defined in {manpage}`systemd.exec(5)`.\n      '';\n      type = lib.types.nullOr lib.types.path;\n    };\n\n    group = lib.mkOption {\n      default = \"configarr\";\n      description = \"Group for the Configarr service.\";\n      type = lib.types.str;\n    };\n\n    package = lib.mkOption {\n      default = import ../package.nix {inherit lib pkgs;};\n      description = \"Package to use\";\n      type = lib.types.package;\n    };\n\n    schedule = lib.mkOption {\n      default = \"daily\";\n      description = \"Run interval for the timer.\";\n      type = lib.types.str;\n    };\n\n    user = lib.mkOption {\n      default = \"configarr\";\n      description = \"User to run the Configarr service as.\";\n      type = lib.types.str;\n    };\n  };\n}\n"
  },
  {
    "path": "pkgs/nix/package.nix",
    "content": "{\n  lib,\n  pkgs,\n  ...\n}:\npkgs.stdenvNoCC.mkDerivation (finalAttrs: {\n  buildPhase = ''\n    runHook preBuild\n    pnpm build\n    runHook postBuild\n  '';\n\n  checkPhase = ''\n    runHook preCheck\n    pnpm test\n    runHook postCheck\n  '';\n\n  CI = \"true\";\n\n  installPhase = ''\n    runHook preInstall\n    install -Dm644 -t $out/share bundle.cjs\n    makeWrapper ${lib.getExe pkgs.nodejs_24} $out/bin/configarr \\\n      --add-flags \"$out/share/bundle.cjs\"\n    runHook postInstall\n  '';\n\n  meta = {\n    changelog = \"https://github.com/raydak-labs/configarr/blob/${finalAttrs.src.rev}/CHANGELOG.md\";\n    description = \"Sync TRaSH Guides + custom configs with Sonarr/Radarr\";\n    homepage = \"https://github.com/raydak-labs/configarr\";\n    license = lib.licenses.agpl3Only;\n    mainProgram = \"configarr\";\n    maintainers = with lib.maintainers; [lord-valen];\n    platforms = lib.platforms.all;\n  };\n\n  nativeBuildInputs = [\n    pkgs.makeBinaryWrapper\n    pkgs.nodejs_24\n    pkgs.pnpm\n    pkgs.pnpmConfigHook\n  ];\n\n  pname = \"configarr\";\n\n  pnpmDeps = pkgs.fetchPnpmDeps {\n    fetcherVersion = 1;\n    hash = \"sha256-qH2H7sJ3rMWutPUSpBtBTtIxQqwEUXFCyj+eoygdfjg=\";\n    inherit (finalAttrs) pname src version;\n  };\n\n  src = pkgs.fetchFromGitHub {\n    owner = \"raydak-labs\";\n    repo = \"configarr\";\n    rev = \"v${finalAttrs.version}\";\n    hash = \"sha256-Kd8EY+qTLv9AQGdLjqW2iU215g/ay9EKcMzTZej9dZk=\";\n  };\n\n  version = \"1.24.0\";\n})\n"
  },
  {
    "path": "playwright.config.ts",
    "content": "import { defineConfig, devices } from \"@playwright/test\";\n\n/**\n * Read environment variables from file.\n * https://github.com/motdotla/dotenv\n */\n// require('dotenv').config();\n\n/**\n * See https://playwright.dev/docs/test-configuration.\n */\nexport default defineConfig({\n  testDir: \"./tests/e2e\",\n  /* Run tests in files in parallel */\n  fullyParallel: true,\n  /* Fail the build on CI if you accidentally left test.only in the source code. */\n  forbidOnly: !!process.env.CI,\n  /* Retry on CI only */\n  retries: process.env.CI ? 2 : 0,\n  /* Opt out of parallel tests on CI. */\n  workers: process.env.CI ? 1 : undefined,\n  /* Reporter to use. See https://playwright.dev/docs/test-reporters */\n  reporter: \"html\",\n  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */\n  use: {\n    /* Base URL to use in actions like `await page.goto('/')`. */\n    // baseURL: 'http://127.0.0.1:3000',\n\n    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */\n    trace: \"on-first-retry\",\n  },\n\n  /* Configure projects for major browsers */\n  projects: [\n    {\n      name: \"chromium\",\n      use: { ...devices[\"Desktop Chrome\"] },\n    },\n\n    {\n      name: \"firefox\",\n      use: { ...devices[\"Desktop Firefox\"] },\n    },\n\n    {\n      name: \"webkit\",\n      use: { ...devices[\"Desktop Safari\"] },\n    },\n\n    /* Test against mobile viewports. */\n    // {\n    //   name: 'Mobile Chrome',\n    //   use: { ...devices['Pixel 5'] },\n    // },\n    // {\n    //   name: 'Mobile Safari',\n    //   use: { ...devices['iPhone 12'] },\n    // },\n\n    /* Test against branded browsers. */\n    // {\n    //   name: 'Microsoft Edge',\n    //   use: { ...devices['Desktop Edge'], channel: 'msedge' },\n    // },\n    // {\n    //   name: 'Google Chrome',\n    //   use: { ...devices['Desktop Chrome'], channel: 'chrome' },\n    // },\n  ],\n\n  /* Run your local dev server before starting the tests */\n  // webServer: {\n  //   command: 'npm run start',\n  //   url: 'http://127.0.0.1:3000',\n  //   reuseExistingServer: !process.env.CI,\n  // },\n});\n"
  },
  {
    "path": "secrets.yml.template",
    "content": "SONARR_API_KEY: replace-with-sonarr-api-key\n"
  },
  {
    "path": "src/__generated__/lidarr/Api.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { ContentType, HttpClient, RequestParams } from \"./../../ky-client\";\nimport {\n  AlbumResource,\n  AlbumResourcePagingResource,\n  AlbumStudioResource,\n  AlbumsMonitoredResource,\n  ArtistEditorResource,\n  ArtistResource,\n  AutoTaggingResource,\n  BackupResource,\n  BlocklistBulkResource,\n  BlocklistResourcePagingResource,\n  CommandResource,\n  CustomFilterResource,\n  CustomFormatBulkResource,\n  CustomFormatResource,\n  DelayProfileResource,\n  DiskSpaceResource,\n  DownloadClientBulkResource,\n  DownloadClientConfigResource,\n  DownloadClientResource,\n  DownloadProtocol,\n  EntityHistoryEventType,\n  HealthResource,\n  HistoryResource,\n  HistoryResourcePagingResource,\n  HostConfigResource,\n  ImportListBulkResource,\n  ImportListExclusionResource,\n  ImportListResource,\n  IndexerBulkResource,\n  IndexerConfigResource,\n  IndexerFlagResource,\n  IndexerResource,\n  LanguageResource,\n  LocalizationResource,\n  LogFileResource,\n  LogResourcePagingResource,\n  ManualImportResource,\n  ManualImportUpdateResource,\n  MediaManagementConfigResource,\n  MetadataProfileResource,\n  MetadataProviderConfigResource,\n  MetadataResource,\n  NamingConfigResource,\n  NotificationResource,\n  ParseResource,\n  QualityDefinitionResource,\n  QualityProfileResource,\n  QueueBulkResource,\n  QueueResource,\n  QueueResourcePagingResource,\n  QueueStatusResource,\n  ReleaseProfileResource,\n  ReleaseResource,\n  RemotePathMappingResource,\n  RenameTrackResource,\n  RetagTrackResource,\n  RootFolderResource,\n  SearchResource,\n  SortDirection,\n  SystemResource,\n  TagDetailsResource,\n  TagResource,\n  TaskResource,\n  TrackFileListResource,\n  TrackFileResource,\n  TrackResource,\n  UiConfigResource,\n  UpdateResource,\n} from \"./data-contracts\";\n\nexport class Api<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Album\n   * @name V1AlbumList\n   * @request GET:/api/v1/album\n   * @secure\n   */\n  v1AlbumList = (\n    query?: {\n      /** @format int32 */\n      artistId?: number;\n      albumIds?: number[];\n      foreignAlbumId?: string;\n      /** @default false */\n      includeAllArtistAlbums?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<AlbumResource[], any>({\n      path: `/api/v1/album`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Album\n   * @name V1AlbumCreate\n   * @request POST:/api/v1/album\n   * @secure\n   */\n  v1AlbumCreate = (data: AlbumResource, params: RequestParams = {}) =>\n    this.http.request<AlbumResource, any>({\n      path: `/api/v1/album`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Album\n   * @name V1AlbumUpdate\n   * @request PUT:/api/v1/album/{id}\n   * @secure\n   */\n  v1AlbumUpdate = (id: string, data: AlbumResource, params: RequestParams = {}) =>\n    this.http.request<AlbumResource, any>({\n      path: `/api/v1/album/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Album\n   * @name V1AlbumDelete\n   * @request DELETE:/api/v1/album/{id}\n   * @secure\n   */\n  v1AlbumDelete = (\n    id: number,\n    query?: {\n      /** @default false */\n      deleteFiles?: boolean;\n      /** @default false */\n      addImportListExclusion?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/album/${id}`,\n      method: \"DELETE\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Album\n   * @name V1AlbumDetail\n   * @request GET:/api/v1/album/{id}\n   * @secure\n   */\n  v1AlbumDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<AlbumResource, any>({\n      path: `/api/v1/album/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Album\n   * @name V1AlbumMonitorUpdate\n   * @request PUT:/api/v1/album/monitor\n   * @secure\n   */\n  v1AlbumMonitorUpdate = (data: AlbumsMonitoredResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/album/monitor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AlbumLookup\n   * @name V1AlbumLookupList\n   * @request GET:/api/v1/album/lookup\n   * @secure\n   */\n  v1AlbumLookupList = (\n    query?: {\n      term?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<AlbumResource[], any>({\n      path: `/api/v1/album/lookup`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AlbumStudio\n   * @name V1AlbumstudioCreate\n   * @request POST:/api/v1/albumstudio\n   * @secure\n   */\n  v1AlbumstudioCreate = (data: AlbumStudioResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/albumstudio`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ApiInfo\n   * @name GetApi\n   * @request GET:/api\n   * @secure\n   */\n  getApi = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Artist\n   * @name V1ArtistDetail\n   * @request GET:/api/v1/artist/{id}\n   * @secure\n   */\n  v1ArtistDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ArtistResource, any>({\n      path: `/api/v1/artist/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Artist\n   * @name V1ArtistUpdate\n   * @request PUT:/api/v1/artist/{id}\n   * @secure\n   */\n  v1ArtistUpdate = (\n    id: string,\n    data: ArtistResource,\n    query?: {\n      /** @default false */\n      moveFiles?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ArtistResource, any>({\n      path: `/api/v1/artist/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Artist\n   * @name V1ArtistDelete\n   * @request DELETE:/api/v1/artist/{id}\n   * @secure\n   */\n  v1ArtistDelete = (\n    id: number,\n    query?: {\n      /** @default false */\n      deleteFiles?: boolean;\n      /** @default false */\n      addImportListExclusion?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/artist/${id}`,\n      method: \"DELETE\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Artist\n   * @name V1ArtistList\n   * @request GET:/api/v1/artist\n   * @secure\n   */\n  v1ArtistList = (\n    query?: {\n      /** @format uuid */\n      mbId?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ArtistResource[], any>({\n      path: `/api/v1/artist`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Artist\n   * @name V1ArtistCreate\n   * @request POST:/api/v1/artist\n   * @secure\n   */\n  v1ArtistCreate = (data: ArtistResource, params: RequestParams = {}) =>\n    this.http.request<ArtistResource, any>({\n      path: `/api/v1/artist`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ArtistEditor\n   * @name V1ArtistEditorUpdate\n   * @request PUT:/api/v1/artist/editor\n   * @secure\n   */\n  v1ArtistEditorUpdate = (data: ArtistEditorResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/artist/editor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ArtistEditor\n   * @name V1ArtistEditorDelete\n   * @request DELETE:/api/v1/artist/editor\n   * @secure\n   */\n  v1ArtistEditorDelete = (data: ArtistEditorResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/artist/editor`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ArtistLookup\n   * @name V1ArtistLookupList\n   * @request GET:/api/v1/artist/lookup\n   * @secure\n   */\n  v1ArtistLookupList = (\n    query?: {\n      term?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ArtistResource[], any>({\n      path: `/api/v1/artist/lookup`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V1AutotaggingDetail\n   * @request GET:/api/v1/autotagging/{id}\n   * @secure\n   */\n  v1AutotaggingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource, any>({\n      path: `/api/v1/autotagging/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V1AutotaggingUpdate\n   * @request PUT:/api/v1/autotagging/{id}\n   * @secure\n   */\n  v1AutotaggingUpdate = (id: string, data: AutoTaggingResource, params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource, any>({\n      path: `/api/v1/autotagging/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V1AutotaggingDelete\n   * @request DELETE:/api/v1/autotagging/{id}\n   * @secure\n   */\n  v1AutotaggingDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/autotagging/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V1AutotaggingCreate\n   * @request POST:/api/v1/autotagging\n   * @secure\n   */\n  v1AutotaggingCreate = (data: AutoTaggingResource, params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource, any>({\n      path: `/api/v1/autotagging`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V1AutotaggingList\n   * @request GET:/api/v1/autotagging\n   * @secure\n   */\n  v1AutotaggingList = (params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource[], any>({\n      path: `/api/v1/autotagging`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V1AutotaggingSchemaList\n   * @request GET:/api/v1/autotagging/schema\n   * @secure\n   */\n  v1AutotaggingSchemaList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/autotagging/schema`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V1SystemBackupList\n   * @request GET:/api/v1/system/backup\n   * @secure\n   */\n  v1SystemBackupList = (params: RequestParams = {}) =>\n    this.http.request<BackupResource[], any>({\n      path: `/api/v1/system/backup`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V1SystemBackupDelete\n   * @request DELETE:/api/v1/system/backup/{id}\n   * @secure\n   */\n  v1SystemBackupDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/backup/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V1SystemBackupRestoreCreate\n   * @request POST:/api/v1/system/backup/restore/{id}\n   * @secure\n   */\n  v1SystemBackupRestoreCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/backup/restore/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V1SystemBackupRestoreUploadCreate\n   * @request POST:/api/v1/system/backup/restore/upload\n   * @secure\n   */\n  v1SystemBackupRestoreUploadCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/backup/restore/upload`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V1BlocklistList\n   * @request GET:/api/v1/blocklist\n   * @secure\n   */\n  v1BlocklistList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<BlocklistResourcePagingResource, any>({\n      path: `/api/v1/blocklist`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V1BlocklistDelete\n   * @request DELETE:/api/v1/blocklist/{id}\n   * @secure\n   */\n  v1BlocklistDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/blocklist/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V1BlocklistBulkDelete\n   * @request DELETE:/api/v1/blocklist/bulk\n   * @secure\n   */\n  v1BlocklistBulkDelete = (data: BlocklistBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/blocklist/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Calendar\n   * @name V1CalendarList\n   * @request GET:/api/v1/calendar\n   * @secure\n   */\n  v1CalendarList = (\n    query?: {\n      /** @format date-time */\n      start?: string;\n      /** @format date-time */\n      end?: string;\n      /** @default false */\n      unmonitored?: boolean;\n      /** @default false */\n      includeArtist?: boolean;\n      /** @default \"\" */\n      tags?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<AlbumResource[], any>({\n      path: `/api/v1/calendar`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Calendar\n   * @name V1CalendarDetail\n   * @request GET:/api/v1/calendar/{id}\n   * @secure\n   */\n  v1CalendarDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<AlbumResource, any>({\n      path: `/api/v1/calendar/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V1CommandDetail\n   * @request GET:/api/v1/command/{id}\n   * @secure\n   */\n  v1CommandDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CommandResource, any>({\n      path: `/api/v1/command/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V1CommandDelete\n   * @request DELETE:/api/v1/command/{id}\n   * @secure\n   */\n  v1CommandDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/command/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V1CommandCreate\n   * @request POST:/api/v1/command\n   * @secure\n   */\n  v1CommandCreate = (data: CommandResource, params: RequestParams = {}) =>\n    this.http.request<CommandResource, any>({\n      path: `/api/v1/command`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V1CommandList\n   * @request GET:/api/v1/command\n   * @secure\n   */\n  v1CommandList = (params: RequestParams = {}) =>\n    this.http.request<CommandResource[], any>({\n      path: `/api/v1/command`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V1CustomfilterDetail\n   * @request GET:/api/v1/customfilter/{id}\n   * @secure\n   */\n  v1CustomfilterDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v1/customfilter/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V1CustomfilterUpdate\n   * @request PUT:/api/v1/customfilter/{id}\n   * @secure\n   */\n  v1CustomfilterUpdate = (id: string, data: CustomFilterResource, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v1/customfilter/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V1CustomfilterDelete\n   * @request DELETE:/api/v1/customfilter/{id}\n   * @secure\n   */\n  v1CustomfilterDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/customfilter/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V1CustomfilterList\n   * @request GET:/api/v1/customfilter\n   * @secure\n   */\n  v1CustomfilterList = (params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource[], any>({\n      path: `/api/v1/customfilter`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V1CustomfilterCreate\n   * @request POST:/api/v1/customfilter\n   * @secure\n   */\n  v1CustomfilterCreate = (data: CustomFilterResource, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v1/customfilter`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatDetail\n   * @request GET:/api/v1/customformat/{id}\n   * @secure\n   */\n  v1CustomformatDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v1/customformat/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatUpdate\n   * @request PUT:/api/v1/customformat/{id}\n   * @secure\n   */\n  v1CustomformatUpdate = (id: string, data: CustomFormatResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v1/customformat/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatDelete\n   * @request DELETE:/api/v1/customformat/{id}\n   * @secure\n   */\n  v1CustomformatDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/customformat/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatList\n   * @request GET:/api/v1/customformat\n   * @secure\n   */\n  v1CustomformatList = (params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource[], any>({\n      path: `/api/v1/customformat`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatCreate\n   * @request POST:/api/v1/customformat\n   * @secure\n   */\n  v1CustomformatCreate = (data: CustomFormatResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v1/customformat`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatBulkUpdate\n   * @request PUT:/api/v1/customformat/bulk\n   * @secure\n   */\n  v1CustomformatBulkUpdate = (data: CustomFormatBulkResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v1/customformat/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatBulkDelete\n   * @request DELETE:/api/v1/customformat/bulk\n   * @secure\n   */\n  v1CustomformatBulkDelete = (data: CustomFormatBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/customformat/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatSchemaList\n   * @request GET:/api/v1/customformat/schema\n   * @secure\n   */\n  v1CustomformatSchemaList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/customformat/schema`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Cutoff\n   * @name V1WantedCutoffList\n   * @request GET:/api/v1/wanted/cutoff\n   * @secure\n   */\n  v1WantedCutoffList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeArtist?: boolean;\n      /** @default true */\n      monitored?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<AlbumResourcePagingResource, any>({\n      path: `/api/v1/wanted/cutoff`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Cutoff\n   * @name V1WantedCutoffDetail\n   * @request GET:/api/v1/wanted/cutoff/{id}\n   * @secure\n   */\n  v1WantedCutoffDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<AlbumResource, any>({\n      path: `/api/v1/wanted/cutoff/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V1DelayprofileCreate\n   * @request POST:/api/v1/delayprofile\n   * @secure\n   */\n  v1DelayprofileCreate = (data: DelayProfileResource, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v1/delayprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V1DelayprofileList\n   * @request GET:/api/v1/delayprofile\n   * @secure\n   */\n  v1DelayprofileList = (params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource[], any>({\n      path: `/api/v1/delayprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V1DelayprofileDelete\n   * @request DELETE:/api/v1/delayprofile/{id}\n   * @secure\n   */\n  v1DelayprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/delayprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V1DelayprofileUpdate\n   * @request PUT:/api/v1/delayprofile/{id}\n   * @secure\n   */\n  v1DelayprofileUpdate = (id: string, data: DelayProfileResource, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v1/delayprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V1DelayprofileDetail\n   * @request GET:/api/v1/delayprofile/{id}\n   * @secure\n   */\n  v1DelayprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v1/delayprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V1DelayprofileReorderUpdate\n   * @request PUT:/api/v1/delayprofile/reorder/{id}\n   * @secure\n   */\n  v1DelayprofileReorderUpdate = (\n    id: number,\n    query?: {\n      /** @format int32 */\n      afterId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/delayprofile/reorder/${id}`,\n      method: \"PUT\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DiskSpace\n   * @name V1DiskspaceList\n   * @request GET:/api/v1/diskspace\n   * @secure\n   */\n  v1DiskspaceList = (params: RequestParams = {}) =>\n    this.http.request<DiskSpaceResource[], any>({\n      path: `/api/v1/diskspace`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientDetail\n   * @request GET:/api/v1/downloadclient/{id}\n   * @secure\n   */\n  v1DownloadclientDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v1/downloadclient/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientUpdate\n   * @request PUT:/api/v1/downloadclient/{id}\n   * @secure\n   */\n  v1DownloadclientUpdate = (\n    id: number,\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v1/downloadclient/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientDelete\n   * @request DELETE:/api/v1/downloadclient/{id}\n   * @secure\n   */\n  v1DownloadclientDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/downloadclient/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientList\n   * @request GET:/api/v1/downloadclient\n   * @secure\n   */\n  v1DownloadclientList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource[], any>({\n      path: `/api/v1/downloadclient`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientCreate\n   * @request POST:/api/v1/downloadclient\n   * @secure\n   */\n  v1DownloadclientCreate = (\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v1/downloadclient`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientBulkUpdate\n   * @request PUT:/api/v1/downloadclient/bulk\n   * @secure\n   */\n  v1DownloadclientBulkUpdate = (data: DownloadClientBulkResource, params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v1/downloadclient/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientBulkDelete\n   * @request DELETE:/api/v1/downloadclient/bulk\n   * @secure\n   */\n  v1DownloadclientBulkDelete = (data: DownloadClientBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/downloadclient/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientSchemaList\n   * @request GET:/api/v1/downloadclient/schema\n   * @secure\n   */\n  v1DownloadclientSchemaList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource[], any>({\n      path: `/api/v1/downloadclient/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientTestCreate\n   * @request POST:/api/v1/downloadclient/test\n   * @secure\n   */\n  v1DownloadclientTestCreate = (\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/downloadclient/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientTestallCreate\n   * @request POST:/api/v1/downloadclient/testall\n   * @secure\n   */\n  v1DownloadclientTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/downloadclient/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientActionCreate\n   * @request POST:/api/v1/downloadclient/action/{name}\n   * @secure\n   */\n  v1DownloadclientActionCreate = (name: string, data: DownloadClientResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/downloadclient/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V1ConfigDownloadclientDetail\n   * @request GET:/api/v1/config/downloadclient/{id}\n   * @secure\n   */\n  v1ConfigDownloadclientDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v1/config/downloadclient/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V1ConfigDownloadclientUpdate\n   * @request PUT:/api/v1/config/downloadclient/{id}\n   * @secure\n   */\n  v1ConfigDownloadclientUpdate = (id: string, data: DownloadClientConfigResource, params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v1/config/downloadclient/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V1ConfigDownloadclientList\n   * @request GET:/api/v1/config/downloadclient\n   * @secure\n   */\n  v1ConfigDownloadclientList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v1/config/downloadclient`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V1FilesystemList\n   * @request GET:/api/v1/filesystem\n   * @secure\n   */\n  v1FilesystemList = (\n    query?: {\n      path?: string;\n      /** @default false */\n      includeFiles?: boolean;\n      /** @default false */\n      allowFoldersWithoutTrailingSlashes?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/filesystem`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V1FilesystemTypeList\n   * @request GET:/api/v1/filesystem/type\n   * @secure\n   */\n  v1FilesystemTypeList = (\n    query?: {\n      path?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/filesystem/type`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V1FilesystemMediafilesList\n   * @request GET:/api/v1/filesystem/mediafiles\n   * @secure\n   */\n  v1FilesystemMediafilesList = (\n    query?: {\n      path?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/filesystem/mediafiles`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Health\n   * @name V1HealthList\n   * @request GET:/api/v1/health\n   * @secure\n   */\n  v1HealthList = (params: RequestParams = {}) =>\n    this.http.request<HealthResource[], any>({\n      path: `/api/v1/health`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V1HistoryList\n   * @request GET:/api/v1/history\n   * @secure\n   */\n  v1HistoryList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      includeArtist?: boolean;\n      includeAlbum?: boolean;\n      includeTrack?: boolean;\n      eventType?: number[];\n      /** @format int32 */\n      albumId?: number;\n      downloadId?: string;\n      artistIds?: number[];\n      quality?: number[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResourcePagingResource, any>({\n      path: `/api/v1/history`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V1HistorySinceList\n   * @request GET:/api/v1/history/since\n   * @secure\n   */\n  v1HistorySinceList = (\n    query?: {\n      /** @format date-time */\n      date?: string;\n      eventType?: EntityHistoryEventType;\n      /** @default false */\n      includeArtist?: boolean;\n      /** @default false */\n      includeAlbum?: boolean;\n      /** @default false */\n      includeTrack?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResource[], any>({\n      path: `/api/v1/history/since`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V1HistoryArtistList\n   * @request GET:/api/v1/history/artist\n   * @secure\n   */\n  v1HistoryArtistList = (\n    query?: {\n      /** @format int32 */\n      artistId?: number;\n      /** @format int32 */\n      albumId?: number;\n      eventType?: EntityHistoryEventType;\n      /** @default false */\n      includeArtist?: boolean;\n      /** @default false */\n      includeAlbum?: boolean;\n      /** @default false */\n      includeTrack?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResource[], any>({\n      path: `/api/v1/history/artist`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V1HistoryFailedCreate\n   * @request POST:/api/v1/history/failed/{id}\n   * @secure\n   */\n  v1HistoryFailedCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/history/failed/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V1ConfigHostDetail\n   * @request GET:/api/v1/config/host/{id}\n   * @secure\n   */\n  v1ConfigHostDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v1/config/host/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V1ConfigHostUpdate\n   * @request PUT:/api/v1/config/host/{id}\n   * @secure\n   */\n  v1ConfigHostUpdate = (id: string, data: HostConfigResource, params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v1/config/host/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V1ConfigHostList\n   * @request GET:/api/v1/config/host\n   * @secure\n   */\n  v1ConfigHostList = (params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v1/config/host`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistDetail\n   * @request GET:/api/v1/importlist/{id}\n   * @secure\n   */\n  v1ImportlistDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v1/importlist/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistUpdate\n   * @request PUT:/api/v1/importlist/{id}\n   * @secure\n   */\n  v1ImportlistUpdate = (\n    id: number,\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v1/importlist/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistDelete\n   * @request DELETE:/api/v1/importlist/{id}\n   * @secure\n   */\n  v1ImportlistDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/importlist/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistList\n   * @request GET:/api/v1/importlist\n   * @secure\n   */\n  v1ImportlistList = (params: RequestParams = {}) =>\n    this.http.request<ImportListResource[], any>({\n      path: `/api/v1/importlist`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistCreate\n   * @request POST:/api/v1/importlist\n   * @secure\n   */\n  v1ImportlistCreate = (\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v1/importlist`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistBulkUpdate\n   * @request PUT:/api/v1/importlist/bulk\n   * @secure\n   */\n  v1ImportlistBulkUpdate = (data: ImportListBulkResource, params: RequestParams = {}) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v1/importlist/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistBulkDelete\n   * @request DELETE:/api/v1/importlist/bulk\n   * @secure\n   */\n  v1ImportlistBulkDelete = (data: ImportListBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/importlist/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistSchemaList\n   * @request GET:/api/v1/importlist/schema\n   * @secure\n   */\n  v1ImportlistSchemaList = (params: RequestParams = {}) =>\n    this.http.request<ImportListResource[], any>({\n      path: `/api/v1/importlist/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistTestCreate\n   * @request POST:/api/v1/importlist/test\n   * @secure\n   */\n  v1ImportlistTestCreate = (\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/importlist/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistTestallCreate\n   * @request POST:/api/v1/importlist/testall\n   * @secure\n   */\n  v1ImportlistTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/importlist/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistActionCreate\n   * @request POST:/api/v1/importlist/action/{name}\n   * @secure\n   */\n  v1ImportlistActionCreate = (name: string, data: ImportListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/importlist/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V1ImportlistexclusionDetail\n   * @request GET:/api/v1/importlistexclusion/{id}\n   * @secure\n   */\n  v1ImportlistexclusionDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v1/importlistexclusion/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V1ImportlistexclusionUpdate\n   * @request PUT:/api/v1/importlistexclusion/{id}\n   * @secure\n   */\n  v1ImportlistexclusionUpdate = (id: string, data: ImportListExclusionResource, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v1/importlistexclusion/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V1ImportlistexclusionDelete\n   * @request DELETE:/api/v1/importlistexclusion/{id}\n   * @secure\n   */\n  v1ImportlistexclusionDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/importlistexclusion/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V1ImportlistexclusionList\n   * @request GET:/api/v1/importlistexclusion\n   * @secure\n   */\n  v1ImportlistexclusionList = (params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource[], any>({\n      path: `/api/v1/importlistexclusion`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V1ImportlistexclusionCreate\n   * @request POST:/api/v1/importlistexclusion\n   * @secure\n   */\n  v1ImportlistexclusionCreate = (data: ImportListExclusionResource, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v1/importlistexclusion`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerDetail\n   * @request GET:/api/v1/indexer/{id}\n   * @secure\n   */\n  v1IndexerDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v1/indexer/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerUpdate\n   * @request PUT:/api/v1/indexer/{id}\n   * @secure\n   */\n  v1IndexerUpdate = (\n    id: number,\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v1/indexer/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerDelete\n   * @request DELETE:/api/v1/indexer/{id}\n   * @secure\n   */\n  v1IndexerDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/indexer/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerList\n   * @request GET:/api/v1/indexer\n   * @secure\n   */\n  v1IndexerList = (params: RequestParams = {}) =>\n    this.http.request<IndexerResource[], any>({\n      path: `/api/v1/indexer`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerCreate\n   * @request POST:/api/v1/indexer\n   * @secure\n   */\n  v1IndexerCreate = (\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v1/indexer`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerBulkUpdate\n   * @request PUT:/api/v1/indexer/bulk\n   * @secure\n   */\n  v1IndexerBulkUpdate = (data: IndexerBulkResource, params: RequestParams = {}) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v1/indexer/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerBulkDelete\n   * @request DELETE:/api/v1/indexer/bulk\n   * @secure\n   */\n  v1IndexerBulkDelete = (data: IndexerBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/indexer/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerSchemaList\n   * @request GET:/api/v1/indexer/schema\n   * @secure\n   */\n  v1IndexerSchemaList = (params: RequestParams = {}) =>\n    this.http.request<IndexerResource[], any>({\n      path: `/api/v1/indexer/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerTestCreate\n   * @request POST:/api/v1/indexer/test\n   * @secure\n   */\n  v1IndexerTestCreate = (\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/indexer/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerTestallCreate\n   * @request POST:/api/v1/indexer/testall\n   * @secure\n   */\n  v1IndexerTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/indexer/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerActionCreate\n   * @request POST:/api/v1/indexer/action/{name}\n   * @secure\n   */\n  v1IndexerActionCreate = (name: string, data: IndexerResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/indexer/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V1ConfigIndexerDetail\n   * @request GET:/api/v1/config/indexer/{id}\n   * @secure\n   */\n  v1ConfigIndexerDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v1/config/indexer/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V1ConfigIndexerUpdate\n   * @request PUT:/api/v1/config/indexer/{id}\n   * @secure\n   */\n  v1ConfigIndexerUpdate = (id: string, data: IndexerConfigResource, params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v1/config/indexer/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V1ConfigIndexerList\n   * @request GET:/api/v1/config/indexer\n   * @secure\n   */\n  v1ConfigIndexerList = (params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v1/config/indexer`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerFlag\n   * @name V1IndexerflagList\n   * @request GET:/api/v1/indexerflag\n   * @secure\n   */\n  v1IndexerflagList = (params: RequestParams = {}) =>\n    this.http.request<IndexerFlagResource[], any>({\n      path: `/api/v1/indexerflag`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Language\n   * @name V1LanguageDetail\n   * @request GET:/api/v1/language/{id}\n   * @secure\n   */\n  v1LanguageDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<LanguageResource, any>({\n      path: `/api/v1/language/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Language\n   * @name V1LanguageList\n   * @request GET:/api/v1/language\n   * @secure\n   */\n  v1LanguageList = (params: RequestParams = {}) =>\n    this.http.request<LanguageResource[], any>({\n      path: `/api/v1/language`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Localization\n   * @name V1LocalizationList\n   * @request GET:/api/v1/localization\n   * @secure\n   */\n  v1LocalizationList = (params: RequestParams = {}) =>\n    this.http.request<LocalizationResource, any>({\n      path: `/api/v1/localization`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Log\n   * @name V1LogList\n   * @request GET:/api/v1/log\n   * @secure\n   */\n  v1LogList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      level?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<LogResourcePagingResource, any>({\n      path: `/api/v1/log`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LogFile\n   * @name V1LogFileList\n   * @request GET:/api/v1/log/file\n   * @secure\n   */\n  v1LogFileList = (params: RequestParams = {}) =>\n    this.http.request<LogFileResource[], any>({\n      path: `/api/v1/log/file`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LogFile\n   * @name V1LogFileDetail\n   * @request GET:/api/v1/log/file/{filename}\n   * @secure\n   */\n  v1LogFileDetail = (filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/log/file/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ManualImport\n   * @name V1ManualimportCreate\n   * @request POST:/api/v1/manualimport\n   * @secure\n   */\n  v1ManualimportCreate = (data: ManualImportUpdateResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/manualimport`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ManualImport\n   * @name V1ManualimportList\n   * @request GET:/api/v1/manualimport\n   * @secure\n   */\n  v1ManualimportList = (\n    query?: {\n      folder?: string;\n      downloadId?: string;\n      /** @format int32 */\n      artistId?: number;\n      /** @default true */\n      filterExistingFiles?: boolean;\n      /** @default true */\n      replaceExistingFiles?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ManualImportResource[], any>({\n      path: `/api/v1/manualimport`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaCover\n   * @name V1MediacoverArtistDetail\n   * @request GET:/api/v1/mediacover/artist/{artistId}/{filename}\n   * @secure\n   */\n  v1MediacoverArtistDetail = (artistId: number, filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/mediacover/artist/${artistId}/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaCover\n   * @name V1MediacoverAlbumDetail\n   * @request GET:/api/v1/mediacover/album/{albumId}/{filename}\n   * @secure\n   */\n  v1MediacoverAlbumDetail = (albumId: number, filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/mediacover/album/${albumId}/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V1ConfigMediamanagementDetail\n   * @request GET:/api/v1/config/mediamanagement/{id}\n   * @secure\n   */\n  v1ConfigMediamanagementDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v1/config/mediamanagement/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V1ConfigMediamanagementUpdate\n   * @request PUT:/api/v1/config/mediamanagement/{id}\n   * @secure\n   */\n  v1ConfigMediamanagementUpdate = (id: string, data: MediaManagementConfigResource, params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v1/config/mediamanagement/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V1ConfigMediamanagementList\n   * @request GET:/api/v1/config/mediamanagement\n   * @secure\n   */\n  v1ConfigMediamanagementList = (params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v1/config/mediamanagement`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataDetail\n   * @request GET:/api/v1/metadata/{id}\n   * @secure\n   */\n  v1MetadataDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v1/metadata/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataUpdate\n   * @request PUT:/api/v1/metadata/{id}\n   * @secure\n   */\n  v1MetadataUpdate = (\n    id: number,\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v1/metadata/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataDelete\n   * @request DELETE:/api/v1/metadata/{id}\n   * @secure\n   */\n  v1MetadataDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/metadata/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataList\n   * @request GET:/api/v1/metadata\n   * @secure\n   */\n  v1MetadataList = (params: RequestParams = {}) =>\n    this.http.request<MetadataResource[], any>({\n      path: `/api/v1/metadata`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataCreate\n   * @request POST:/api/v1/metadata\n   * @secure\n   */\n  v1MetadataCreate = (\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v1/metadata`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataSchemaList\n   * @request GET:/api/v1/metadata/schema\n   * @secure\n   */\n  v1MetadataSchemaList = (params: RequestParams = {}) =>\n    this.http.request<MetadataResource[], any>({\n      path: `/api/v1/metadata/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataTestCreate\n   * @request POST:/api/v1/metadata/test\n   * @secure\n   */\n  v1MetadataTestCreate = (\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/metadata/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataTestallCreate\n   * @request POST:/api/v1/metadata/testall\n   * @secure\n   */\n  v1MetadataTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/metadata/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataActionCreate\n   * @request POST:/api/v1/metadata/action/{name}\n   * @secure\n   */\n  v1MetadataActionCreate = (name: string, data: MetadataResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/metadata/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProfile\n   * @name V1MetadataprofileCreate\n   * @request POST:/api/v1/metadataprofile\n   * @secure\n   */\n  v1MetadataprofileCreate = (data: MetadataProfileResource, params: RequestParams = {}) =>\n    this.http.request<MetadataProfileResource, any>({\n      path: `/api/v1/metadataprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProfile\n   * @name V1MetadataprofileList\n   * @request GET:/api/v1/metadataprofile\n   * @secure\n   */\n  v1MetadataprofileList = (params: RequestParams = {}) =>\n    this.http.request<MetadataProfileResource[], any>({\n      path: `/api/v1/metadataprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProfile\n   * @name V1MetadataprofileDelete\n   * @request DELETE:/api/v1/metadataprofile/{id}\n   * @secure\n   */\n  v1MetadataprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/metadataprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProfile\n   * @name V1MetadataprofileUpdate\n   * @request PUT:/api/v1/metadataprofile/{id}\n   * @secure\n   */\n  v1MetadataprofileUpdate = (id: string, data: MetadataProfileResource, params: RequestParams = {}) =>\n    this.http.request<MetadataProfileResource, any>({\n      path: `/api/v1/metadataprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProfile\n   * @name V1MetadataprofileDetail\n   * @request GET:/api/v1/metadataprofile/{id}\n   * @secure\n   */\n  v1MetadataprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MetadataProfileResource, any>({\n      path: `/api/v1/metadataprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProfileSchema\n   * @name V1MetadataprofileSchemaList\n   * @request GET:/api/v1/metadataprofile/schema\n   * @secure\n   */\n  v1MetadataprofileSchemaList = (params: RequestParams = {}) =>\n    this.http.request<MetadataProfileResource, any>({\n      path: `/api/v1/metadataprofile/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProviderConfig\n   * @name V1ConfigMetadataproviderDetail\n   * @request GET:/api/v1/config/metadataprovider/{id}\n   * @secure\n   */\n  v1ConfigMetadataproviderDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MetadataProviderConfigResource, any>({\n      path: `/api/v1/config/metadataprovider/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProviderConfig\n   * @name V1ConfigMetadataproviderUpdate\n   * @request PUT:/api/v1/config/metadataprovider/{id}\n   * @secure\n   */\n  v1ConfigMetadataproviderUpdate = (id: string, data: MetadataProviderConfigResource, params: RequestParams = {}) =>\n    this.http.request<MetadataProviderConfigResource, any>({\n      path: `/api/v1/config/metadataprovider/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProviderConfig\n   * @name V1ConfigMetadataproviderList\n   * @request GET:/api/v1/config/metadataprovider\n   * @secure\n   */\n  v1ConfigMetadataproviderList = (params: RequestParams = {}) =>\n    this.http.request<MetadataProviderConfigResource, any>({\n      path: `/api/v1/config/metadataprovider`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Missing\n   * @name V1WantedMissingList\n   * @request GET:/api/v1/wanted/missing\n   * @secure\n   */\n  v1WantedMissingList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeArtist?: boolean;\n      /** @default true */\n      monitored?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<AlbumResourcePagingResource, any>({\n      path: `/api/v1/wanted/missing`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Missing\n   * @name V1WantedMissingDetail\n   * @request GET:/api/v1/wanted/missing/{id}\n   * @secure\n   */\n  v1WantedMissingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<AlbumResource, any>({\n      path: `/api/v1/wanted/missing/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V1ConfigNamingDetail\n   * @request GET:/api/v1/config/naming/{id}\n   * @secure\n   */\n  v1ConfigNamingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v1/config/naming/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V1ConfigNamingUpdate\n   * @request PUT:/api/v1/config/naming/{id}\n   * @secure\n   */\n  v1ConfigNamingUpdate = (id: string, data: NamingConfigResource, params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v1/config/naming/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V1ConfigNamingList\n   * @request GET:/api/v1/config/naming\n   * @secure\n   */\n  v1ConfigNamingList = (params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v1/config/naming`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V1ConfigNamingExamplesList\n   * @request GET:/api/v1/config/naming/examples\n   * @secure\n   */\n  v1ConfigNamingExamplesList = (\n    query?: {\n      renameTracks?: boolean;\n      replaceIllegalCharacters?: boolean;\n      /** @format int32 */\n      colonReplacementFormat?: number;\n      standardTrackFormat?: string;\n      multiDiscTrackFormat?: string;\n      artistFolderFormat?: string;\n      includeArtistName?: boolean;\n      includeAlbumTitle?: boolean;\n      includeQuality?: boolean;\n      replaceSpaces?: boolean;\n      separator?: string;\n      numberStyle?: string;\n      /** @format int32 */\n      id?: number;\n      resourceName?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/config/naming/examples`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationDetail\n   * @request GET:/api/v1/notification/{id}\n   * @secure\n   */\n  v1NotificationDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v1/notification/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationUpdate\n   * @request PUT:/api/v1/notification/{id}\n   * @secure\n   */\n  v1NotificationUpdate = (\n    id: number,\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v1/notification/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationDelete\n   * @request DELETE:/api/v1/notification/{id}\n   * @secure\n   */\n  v1NotificationDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/notification/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationList\n   * @request GET:/api/v1/notification\n   * @secure\n   */\n  v1NotificationList = (params: RequestParams = {}) =>\n    this.http.request<NotificationResource[], any>({\n      path: `/api/v1/notification`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationCreate\n   * @request POST:/api/v1/notification\n   * @secure\n   */\n  v1NotificationCreate = (\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v1/notification`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationSchemaList\n   * @request GET:/api/v1/notification/schema\n   * @secure\n   */\n  v1NotificationSchemaList = (params: RequestParams = {}) =>\n    this.http.request<NotificationResource[], any>({\n      path: `/api/v1/notification/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationTestCreate\n   * @request POST:/api/v1/notification/test\n   * @secure\n   */\n  v1NotificationTestCreate = (\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/notification/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationTestallCreate\n   * @request POST:/api/v1/notification/testall\n   * @secure\n   */\n  v1NotificationTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/notification/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationActionCreate\n   * @request POST:/api/v1/notification/action/{name}\n   * @secure\n   */\n  v1NotificationActionCreate = (name: string, data: NotificationResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/notification/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Parse\n   * @name V1ParseList\n   * @request GET:/api/v1/parse\n   * @secure\n   */\n  v1ParseList = (\n    query?: {\n      title?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ParseResource, any>({\n      path: `/api/v1/parse`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V1QualitydefinitionUpdate\n   * @request PUT:/api/v1/qualitydefinition/{id}\n   * @secure\n   */\n  v1QualitydefinitionUpdate = (id: string, data: QualityDefinitionResource, params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource, any>({\n      path: `/api/v1/qualitydefinition/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V1QualitydefinitionDetail\n   * @request GET:/api/v1/qualitydefinition/{id}\n   * @secure\n   */\n  v1QualitydefinitionDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource, any>({\n      path: `/api/v1/qualitydefinition/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V1QualitydefinitionList\n   * @request GET:/api/v1/qualitydefinition\n   * @secure\n   */\n  v1QualitydefinitionList = (params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource[], any>({\n      path: `/api/v1/qualitydefinition`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V1QualitydefinitionUpdateUpdate\n   * @request PUT:/api/v1/qualitydefinition/update\n   * @secure\n   */\n  v1QualitydefinitionUpdateUpdate = (data: QualityDefinitionResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/qualitydefinition/update`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V1QualityprofileCreate\n   * @request POST:/api/v1/qualityprofile\n   * @secure\n   */\n  v1QualityprofileCreate = (data: QualityProfileResource, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v1/qualityprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V1QualityprofileList\n   * @request GET:/api/v1/qualityprofile\n   * @secure\n   */\n  v1QualityprofileList = (params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource[], any>({\n      path: `/api/v1/qualityprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V1QualityprofileDelete\n   * @request DELETE:/api/v1/qualityprofile/{id}\n   * @secure\n   */\n  v1QualityprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/qualityprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V1QualityprofileUpdate\n   * @request PUT:/api/v1/qualityprofile/{id}\n   * @secure\n   */\n  v1QualityprofileUpdate = (id: string, data: QualityProfileResource, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v1/qualityprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V1QualityprofileDetail\n   * @request GET:/api/v1/qualityprofile/{id}\n   * @secure\n   */\n  v1QualityprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v1/qualityprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfileSchema\n   * @name V1QualityprofileSchemaList\n   * @request GET:/api/v1/qualityprofile/schema\n   * @secure\n   */\n  v1QualityprofileSchemaList = (params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v1/qualityprofile/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V1QueueDelete\n   * @request DELETE:/api/v1/queue/{id}\n   * @secure\n   */\n  v1QueueDelete = (\n    id: number,\n    query?: {\n      /** @default true */\n      removeFromClient?: boolean;\n      /** @default false */\n      blocklist?: boolean;\n      /** @default false */\n      skipRedownload?: boolean;\n      /** @default false */\n      changeCategory?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/queue/${id}`,\n      method: \"DELETE\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V1QueueBulkDelete\n   * @request DELETE:/api/v1/queue/bulk\n   * @secure\n   */\n  v1QueueBulkDelete = (\n    data: QueueBulkResource,\n    query?: {\n      /** @default true */\n      removeFromClient?: boolean;\n      /** @default false */\n      blocklist?: boolean;\n      /** @default false */\n      skipRedownload?: boolean;\n      /** @default false */\n      changeCategory?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/queue/bulk`,\n      method: \"DELETE\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V1QueueList\n   * @request GET:/api/v1/queue\n   * @secure\n   */\n  v1QueueList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeUnknownArtistItems?: boolean;\n      /** @default false */\n      includeArtist?: boolean;\n      /** @default false */\n      includeAlbum?: boolean;\n      artistIds?: number[];\n      protocol?: DownloadProtocol;\n      quality?: number[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<QueueResourcePagingResource, any>({\n      path: `/api/v1/queue`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueAction\n   * @name V1QueueGrabCreate\n   * @request POST:/api/v1/queue/grab/{id}\n   * @secure\n   */\n  v1QueueGrabCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/queue/grab/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueAction\n   * @name V1QueueGrabBulkCreate\n   * @request POST:/api/v1/queue/grab/bulk\n   * @secure\n   */\n  v1QueueGrabBulkCreate = (data: QueueBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/queue/grab/bulk`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueDetails\n   * @name V1QueueDetailsList\n   * @request GET:/api/v1/queue/details\n   * @secure\n   */\n  v1QueueDetailsList = (\n    query?: {\n      /** @format int32 */\n      artistId?: number;\n      albumIds?: number[];\n      /** @default false */\n      includeArtist?: boolean;\n      /** @default true */\n      includeAlbum?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<QueueResource[], any>({\n      path: `/api/v1/queue/details`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueStatus\n   * @name V1QueueStatusList\n   * @request GET:/api/v1/queue/status\n   * @secure\n   */\n  v1QueueStatusList = (params: RequestParams = {}) =>\n    this.http.request<QueueStatusResource, any>({\n      path: `/api/v1/queue/status`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Release\n   * @name V1ReleaseCreate\n   * @request POST:/api/v1/release\n   * @secure\n   */\n  v1ReleaseCreate = (data: ReleaseResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseResource, any>({\n      path: `/api/v1/release`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Release\n   * @name V1ReleaseList\n   * @request GET:/api/v1/release\n   * @secure\n   */\n  v1ReleaseList = (\n    query?: {\n      /** @format int32 */\n      albumId?: number;\n      /** @format int32 */\n      artistId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ReleaseResource[], any>({\n      path: `/api/v1/release`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V1ReleaseprofileDetail\n   * @request GET:/api/v1/releaseprofile/{id}\n   * @secure\n   */\n  v1ReleaseprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v1/releaseprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V1ReleaseprofileUpdate\n   * @request PUT:/api/v1/releaseprofile/{id}\n   * @secure\n   */\n  v1ReleaseprofileUpdate = (id: string, data: ReleaseProfileResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v1/releaseprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V1ReleaseprofileDelete\n   * @request DELETE:/api/v1/releaseprofile/{id}\n   * @secure\n   */\n  v1ReleaseprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/releaseprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V1ReleaseprofileList\n   * @request GET:/api/v1/releaseprofile\n   * @secure\n   */\n  v1ReleaseprofileList = (params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource[], any>({\n      path: `/api/v1/releaseprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V1ReleaseprofileCreate\n   * @request POST:/api/v1/releaseprofile\n   * @secure\n   */\n  v1ReleaseprofileCreate = (data: ReleaseProfileResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v1/releaseprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleasePush\n   * @name V1ReleasePushCreate\n   * @request POST:/api/v1/release/push\n   * @secure\n   */\n  v1ReleasePushCreate = (data: ReleaseResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseResource, any>({\n      path: `/api/v1/release/push`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V1RemotepathmappingDetail\n   * @request GET:/api/v1/remotepathmapping/{id}\n   * @secure\n   */\n  v1RemotepathmappingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v1/remotepathmapping/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V1RemotepathmappingDelete\n   * @request DELETE:/api/v1/remotepathmapping/{id}\n   * @secure\n   */\n  v1RemotepathmappingDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/remotepathmapping/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V1RemotepathmappingUpdate\n   * @request PUT:/api/v1/remotepathmapping/{id}\n   * @secure\n   */\n  v1RemotepathmappingUpdate = (id: string, data: RemotePathMappingResource, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v1/remotepathmapping/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V1RemotepathmappingCreate\n   * @request POST:/api/v1/remotepathmapping\n   * @secure\n   */\n  v1RemotepathmappingCreate = (data: RemotePathMappingResource, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v1/remotepathmapping`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V1RemotepathmappingList\n   * @request GET:/api/v1/remotepathmapping\n   * @secure\n   */\n  v1RemotepathmappingList = (params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource[], any>({\n      path: `/api/v1/remotepathmapping`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RenameTrack\n   * @name V1RenameList\n   * @request GET:/api/v1/rename\n   * @secure\n   */\n  v1RenameList = (\n    query?: {\n      /** @format int32 */\n      artistId?: number;\n      /** @format int32 */\n      albumId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<RenameTrackResource[], any>({\n      path: `/api/v1/rename`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RetagTrack\n   * @name V1RetagList\n   * @request GET:/api/v1/retag\n   * @secure\n   */\n  v1RetagList = (\n    query?: {\n      /** @format int32 */\n      artistId?: number;\n      /** @format int32 */\n      albumId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<RetagTrackResource[], any>({\n      path: `/api/v1/retag`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V1RootfolderDetail\n   * @request GET:/api/v1/rootfolder/{id}\n   * @secure\n   */\n  v1RootfolderDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<RootFolderResource, any>({\n      path: `/api/v1/rootfolder/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V1RootfolderUpdate\n   * @request PUT:/api/v1/rootfolder/{id}\n   * @secure\n   */\n  v1RootfolderUpdate = (id: string, data: RootFolderResource, params: RequestParams = {}) =>\n    this.http.request<RootFolderResource, any>({\n      path: `/api/v1/rootfolder/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V1RootfolderDelete\n   * @request DELETE:/api/v1/rootfolder/{id}\n   * @secure\n   */\n  v1RootfolderDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/rootfolder/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V1RootfolderCreate\n   * @request POST:/api/v1/rootfolder\n   * @secure\n   */\n  v1RootfolderCreate = (data: RootFolderResource, params: RequestParams = {}) =>\n    this.http.request<RootFolderResource, any>({\n      path: `/api/v1/rootfolder`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V1RootfolderList\n   * @request GET:/api/v1/rootfolder\n   * @secure\n   */\n  v1RootfolderList = (params: RequestParams = {}) =>\n    this.http.request<RootFolderResource[], any>({\n      path: `/api/v1/rootfolder`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Search\n   * @name V1SearchList\n   * @request GET:/api/v1/search\n   * @secure\n   */\n  v1SearchList = (\n    query?: {\n      term?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<SearchResource[], any>({\n      path: `/api/v1/search`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V1SystemStatusList\n   * @request GET:/api/v1/system/status\n   * @secure\n   */\n  v1SystemStatusList = (params: RequestParams = {}) =>\n    this.http.request<SystemResource, any>({\n      path: `/api/v1/system/status`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V1SystemRoutesList\n   * @request GET:/api/v1/system/routes\n   * @secure\n   */\n  v1SystemRoutesList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/routes`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V1SystemRoutesDuplicateList\n   * @request GET:/api/v1/system/routes/duplicate\n   * @secure\n   */\n  v1SystemRoutesDuplicateList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/routes/duplicate`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V1SystemShutdownCreate\n   * @request POST:/api/v1/system/shutdown\n   * @secure\n   */\n  v1SystemShutdownCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/shutdown`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V1SystemRestartCreate\n   * @request POST:/api/v1/system/restart\n   * @secure\n   */\n  v1SystemRestartCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/restart`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V1TagDetail\n   * @request GET:/api/v1/tag/{id}\n   * @secure\n   */\n  v1TagDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v1/tag/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V1TagUpdate\n   * @request PUT:/api/v1/tag/{id}\n   * @secure\n   */\n  v1TagUpdate = (id: string, data: TagResource, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v1/tag/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V1TagDelete\n   * @request DELETE:/api/v1/tag/{id}\n   * @secure\n   */\n  v1TagDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/tag/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V1TagList\n   * @request GET:/api/v1/tag\n   * @secure\n   */\n  v1TagList = (params: RequestParams = {}) =>\n    this.http.request<TagResource[], any>({\n      path: `/api/v1/tag`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V1TagCreate\n   * @request POST:/api/v1/tag\n   * @secure\n   */\n  v1TagCreate = (data: TagResource, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v1/tag`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TagDetails\n   * @name V1TagDetailDetail\n   * @request GET:/api/v1/tag/detail/{id}\n   * @secure\n   */\n  v1TagDetailDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TagDetailsResource, any>({\n      path: `/api/v1/tag/detail/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TagDetails\n   * @name V1TagDetailList\n   * @request GET:/api/v1/tag/detail\n   * @secure\n   */\n  v1TagDetailList = (params: RequestParams = {}) =>\n    this.http.request<TagDetailsResource[], any>({\n      path: `/api/v1/tag/detail`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Task\n   * @name V1SystemTaskList\n   * @request GET:/api/v1/system/task\n   * @secure\n   */\n  v1SystemTaskList = (params: RequestParams = {}) =>\n    this.http.request<TaskResource[], any>({\n      path: `/api/v1/system/task`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Task\n   * @name V1SystemTaskDetail\n   * @request GET:/api/v1/system/task/{id}\n   * @secure\n   */\n  v1SystemTaskDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TaskResource, any>({\n      path: `/api/v1/system/task/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Track\n   * @name V1TrackList\n   * @request GET:/api/v1/track\n   * @secure\n   */\n  v1TrackList = (\n    query?: {\n      /** @format int32 */\n      artistId?: number;\n      /** @format int32 */\n      albumId?: number;\n      /** @format int32 */\n      albumReleaseId?: number;\n      trackIds?: number[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<TrackResource[], any>({\n      path: `/api/v1/track`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Track\n   * @name V1TrackDetail\n   * @request GET:/api/v1/track/{id}\n   * @secure\n   */\n  v1TrackDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TrackResource, any>({\n      path: `/api/v1/track/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TrackFile\n   * @name V1TrackfileDetail\n   * @request GET:/api/v1/trackfile/{id}\n   * @secure\n   */\n  v1TrackfileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TrackFileResource, any>({\n      path: `/api/v1/trackfile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TrackFile\n   * @name V1TrackfileUpdate\n   * @request PUT:/api/v1/trackfile/{id}\n   * @secure\n   */\n  v1TrackfileUpdate = (id: string, data: TrackFileResource, params: RequestParams = {}) =>\n    this.http.request<TrackFileResource, any>({\n      path: `/api/v1/trackfile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TrackFile\n   * @name V1TrackfileDelete\n   * @request DELETE:/api/v1/trackfile/{id}\n   * @secure\n   */\n  v1TrackfileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/trackfile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TrackFile\n   * @name V1TrackfileList\n   * @request GET:/api/v1/trackfile\n   * @secure\n   */\n  v1TrackfileList = (\n    query?: {\n      /** @format int32 */\n      artistId?: number;\n      trackFileIds?: number[];\n      albumId?: number[];\n      unmapped?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<TrackFileResource[], any>({\n      path: `/api/v1/trackfile`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TrackFile\n   * @name V1TrackfileEditorUpdate\n   * @request PUT:/api/v1/trackfile/editor\n   * @secure\n   */\n  v1TrackfileEditorUpdate = (data: TrackFileListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/trackfile/editor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TrackFile\n   * @name V1TrackfileBulkDelete\n   * @request DELETE:/api/v1/trackfile/bulk\n   * @secure\n   */\n  v1TrackfileBulkDelete = (data: TrackFileListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/trackfile/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V1ConfigUiUpdate\n   * @request PUT:/api/v1/config/ui/{id}\n   * @secure\n   */\n  v1ConfigUiUpdate = (id: string, data: UiConfigResource, params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v1/config/ui/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V1ConfigUiDetail\n   * @request GET:/api/v1/config/ui/{id}\n   * @secure\n   */\n  v1ConfigUiDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v1/config/ui/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V1ConfigUiList\n   * @request GET:/api/v1/config/ui\n   * @secure\n   */\n  v1ConfigUiList = (params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v1/config/ui`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Update\n   * @name V1UpdateList\n   * @request GET:/api/v1/update\n   * @secure\n   */\n  v1UpdateList = (params: RequestParams = {}) =>\n    this.http.request<UpdateResource[], any>({\n      path: `/api/v1/update`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UpdateLogFile\n   * @name V1LogFileUpdateList\n   * @request GET:/api/v1/log/file/update\n   * @secure\n   */\n  v1LogFileUpdateList = (params: RequestParams = {}) =>\n    this.http.request<LogFileResource[], any>({\n      path: `/api/v1/log/file/update`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UpdateLogFile\n   * @name V1LogFileUpdateDetail\n   * @request GET:/api/v1/log/file/update/{filename}\n   * @secure\n   */\n  v1LogFileUpdateDetail = (filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/log/file/update/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/lidarr/Content.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Content<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name ContentDetail\n   * @request GET:/content/{path}\n   * @secure\n   */\n  contentDetail = (path: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/content/${path}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/lidarr/Feed.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Feed<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags CalendarFeed\n   * @name V1CalendarLidarrIcsList\n   * @request GET:/feed/v1/calendar/lidarr.ics\n   * @secure\n   */\n  v1CalendarLidarrIcsList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 7\n       */\n      pastDays?: number;\n      /**\n       * @format int32\n       * @default 28\n       */\n      futureDays?: number;\n      /** @default \"\" */\n      tags?: string;\n      /** @default false */\n      unmonitored?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/feed/v1/calendar/lidarr.ics`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/lidarr/Login.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { ContentType, HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Login<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Authentication\n   * @name LoginCreate\n   * @request POST:/login\n   * @secure\n   */\n  loginCreate = (\n    data: {\n      username?: string;\n      password?: string;\n      rememberMe?: string;\n    },\n    query?: {\n      returnUrl?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/login`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.FormData,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name LoginList\n   * @request GET:/login\n   * @secure\n   */\n  loginList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/login`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/lidarr/Logout.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Logout<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Authentication\n   * @name LogoutList\n   * @request GET:/logout\n   * @secure\n   */\n  logoutList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/logout`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/lidarr/Path.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Path<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name GetPath\n   * @request GET:/{path}\n   * @secure\n   */\n  getPath = (path: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/${path}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/lidarr/Ping.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\nimport { PingResource } from \"./data-contracts\";\n\nexport class Ping<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Ping\n   * @name PingList\n   * @request GET:/ping\n   * @secure\n   */\n  pingList = (params: RequestParams = {}) =>\n    this.http.request<PingResource, any>({\n      path: `/ping`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Ping\n   * @name HeadPing\n   * @request HEAD:/ping\n   * @secure\n   */\n  headPing = (params: RequestParams = {}) =>\n    this.http.request<PingResource, any>({\n      path: `/ping`,\n      method: \"HEAD\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/lidarr/data-contracts.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nexport enum WriteAudioTagsType {\n  No = \"no\",\n  NewFiles = \"newFiles\",\n  AllFiles = \"allFiles\",\n  Sync = \"sync\",\n}\n\nexport enum UpdateMechanism {\n  BuiltIn = \"builtIn\",\n  Script = \"script\",\n  External = \"external\",\n  Apt = \"apt\",\n  Docker = \"docker\",\n}\n\nexport enum TrackedDownloadStatus {\n  Ok = \"ok\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum TrackedDownloadState {\n  Downloading = \"downloading\",\n  DownloadFailed = \"downloadFailed\",\n  DownloadFailedPending = \"downloadFailedPending\",\n  ImportBlocked = \"importBlocked\",\n  ImportPending = \"importPending\",\n  Importing = \"importing\",\n  ImportFailed = \"importFailed\",\n  Imported = \"imported\",\n  Ignored = \"ignored\",\n}\n\nexport enum SortDirection {\n  Default = \"default\",\n  Ascending = \"ascending\",\n  Descending = \"descending\",\n}\n\nexport enum RuntimeMode {\n  Console = \"console\",\n  Service = \"service\",\n  Tray = \"tray\",\n}\n\nexport enum RescanAfterRefreshType {\n  Always = \"always\",\n  AfterManual = \"afterManual\",\n  Never = \"never\",\n}\n\nexport enum RejectionType {\n  Permanent = \"permanent\",\n  Temporary = \"temporary\",\n}\n\nexport enum ProxyType {\n  Http = \"http\",\n  Socks4 = \"socks4\",\n  Socks5 = \"socks5\",\n}\n\nexport enum ProviderMessageType {\n  Info = \"info\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum ProperDownloadTypes {\n  PreferAndUpgrade = \"preferAndUpgrade\",\n  DoNotUpgrade = \"doNotUpgrade\",\n  DoNotPrefer = \"doNotPrefer\",\n}\n\nexport enum PrivacyLevel {\n  Normal = \"normal\",\n  Password = \"password\",\n  ApiKey = \"apiKey\",\n  UserName = \"userName\",\n}\n\nexport enum NewItemMonitorTypes {\n  All = \"all\",\n  None = \"none\",\n  New = \"new\",\n}\n\nexport enum MonitorTypes {\n  All = \"all\",\n  Future = \"future\",\n  Missing = \"missing\",\n  Existing = \"existing\",\n  Latest = \"latest\",\n  First = \"first\",\n  None = \"none\",\n  Unknown = \"unknown\",\n}\n\nexport enum MediaCoverTypes {\n  Unknown = \"unknown\",\n  Poster = \"poster\",\n  Banner = \"banner\",\n  Fanart = \"fanart\",\n  Screenshot = \"screenshot\",\n  Headshot = \"headshot\",\n  Cover = \"cover\",\n  Disc = \"disc\",\n  Logo = \"logo\",\n  Clearlogo = \"clearlogo\",\n}\n\nexport enum ImportListType {\n  Program = \"program\",\n  Spotify = \"spotify\",\n  LastFm = \"lastFm\",\n  Other = \"other\",\n  Advanced = \"advanced\",\n}\n\nexport enum ImportListMonitorType {\n  None = \"none\",\n  SpecificAlbum = \"specificAlbum\",\n  EntireArtist = \"entireArtist\",\n}\n\nexport enum HealthCheckResult {\n  Ok = \"ok\",\n  Notice = \"notice\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum FileDateType {\n  None = \"none\",\n  AlbumReleaseDate = \"albumReleaseDate\",\n}\n\nexport enum EntityHistoryEventType {\n  Unknown = \"unknown\",\n  Grabbed = \"grabbed\",\n  ArtistFolderImported = \"artistFolderImported\",\n  TrackFileImported = \"trackFileImported\",\n  DownloadFailed = \"downloadFailed\",\n  TrackFileDeleted = \"trackFileDeleted\",\n  TrackFileRenamed = \"trackFileRenamed\",\n  AlbumImportIncomplete = \"albumImportIncomplete\",\n  DownloadImported = \"downloadImported\",\n  TrackFileRetagged = \"trackFileRetagged\",\n  DownloadIgnored = \"downloadIgnored\",\n}\n\nexport enum DownloadProtocol {\n  Unknown = \"unknown\",\n  Usenet = \"usenet\",\n  Torrent = \"torrent\",\n}\n\nexport enum DatabaseType {\n  SqLite = \"sqLite\",\n  PostgreSQL = \"postgreSQL\",\n}\n\nexport enum CommandTrigger {\n  Unspecified = \"unspecified\",\n  Manual = \"manual\",\n  Scheduled = \"scheduled\",\n}\n\nexport enum CommandStatus {\n  Queued = \"queued\",\n  Started = \"started\",\n  Completed = \"completed\",\n  Failed = \"failed\",\n  Aborted = \"aborted\",\n  Cancelled = \"cancelled\",\n  Orphaned = \"orphaned\",\n}\n\nexport enum CommandResult {\n  Unknown = \"unknown\",\n  Successful = \"successful\",\n  Unsuccessful = \"unsuccessful\",\n}\n\nexport enum CommandPriority {\n  Normal = \"normal\",\n  High = \"high\",\n  Low = \"low\",\n}\n\nexport enum CertificateValidationType {\n  Enabled = \"enabled\",\n  DisabledForLocalAddresses = \"disabledForLocalAddresses\",\n  Disabled = \"disabled\",\n}\n\nexport enum BackupType {\n  Scheduled = \"scheduled\",\n  Manual = \"manual\",\n  Update = \"update\",\n}\n\nexport enum AuthenticationType {\n  None = \"none\",\n  Basic = \"basic\",\n  Forms = \"forms\",\n  External = \"external\",\n}\n\nexport enum AuthenticationRequiredType {\n  Enabled = \"enabled\",\n  DisabledForLocalAddresses = \"disabledForLocalAddresses\",\n}\n\nexport enum ArtistStatusType {\n  Continuing = \"continuing\",\n  Ended = \"ended\",\n  Deleted = \"deleted\",\n}\n\nexport enum ApplyTags {\n  Add = \"add\",\n  Remove = \"remove\",\n  Replace = \"replace\",\n}\n\nexport enum AllowFingerprinting {\n  Never = \"never\",\n  NewFiles = \"newFiles\",\n  AllFiles = \"allFiles\",\n}\n\nexport enum AlbumAddType {\n  Automatic = \"automatic\",\n  Manual = \"manual\",\n}\n\nexport interface AddAlbumOptions {\n  addType?: AlbumAddType;\n  searchForNewAlbum?: boolean;\n}\n\nexport interface AddArtistOptions {\n  monitor?: MonitorTypes;\n  albumsToMonitor?: string[] | null;\n  monitored?: boolean;\n  searchForMissingAlbums?: boolean;\n}\n\nexport interface AlbumReleaseResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  albumId?: number;\n  foreignReleaseId?: string | null;\n  title?: string | null;\n  status?: string | null;\n  /** @format int32 */\n  duration?: number;\n  /** @format int32 */\n  trackCount?: number;\n  media?: MediumResource[] | null;\n  /** @format int32 */\n  mediumCount?: number;\n  disambiguation?: string | null;\n  country?: string[] | null;\n  label?: string[] | null;\n  format?: string | null;\n  monitored?: boolean;\n}\n\nexport interface AlbumResource {\n  /** @format int32 */\n  id?: number;\n  title?: string | null;\n  disambiguation?: string | null;\n  overview?: string | null;\n  /** @format int32 */\n  artistId?: number;\n  foreignAlbumId?: string | null;\n  monitored?: boolean;\n  anyReleaseOk?: boolean;\n  /** @format int32 */\n  profileId?: number;\n  /** @format int32 */\n  duration?: number;\n  albumType?: string | null;\n  secondaryTypes?: string[] | null;\n  /** @format int32 */\n  mediumCount?: number;\n  ratings?: Ratings;\n  /** @format date-time */\n  releaseDate?: string | null;\n  releases?: AlbumReleaseResource[] | null;\n  genres?: string[] | null;\n  media?: MediumResource[] | null;\n  artist?: ArtistResource;\n  images?: MediaCover[] | null;\n  links?: Links[] | null;\n  /** @format date-time */\n  lastSearchTime?: string | null;\n  statistics?: AlbumStatisticsResource;\n  addOptions?: AddAlbumOptions;\n  remoteCover?: string | null;\n}\n\nexport interface AlbumResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: AlbumResource[] | null;\n}\n\nexport interface AlbumStatisticsResource {\n  /** @format int32 */\n  trackFileCount?: number;\n  /** @format int32 */\n  trackCount?: number;\n  /** @format int32 */\n  totalTrackCount?: number;\n  /** @format int64 */\n  sizeOnDisk?: number;\n  /** @format double */\n  percentOfTracks?: number;\n}\n\nexport interface AlbumStudioArtistResource {\n  /** @format int32 */\n  id?: number;\n  monitored?: boolean | null;\n  albums?: AlbumResource[] | null;\n}\n\nexport interface AlbumStudioResource {\n  artist?: AlbumStudioArtistResource[] | null;\n  monitoringOptions?: MonitoringOptions;\n  monitorNewItems?: NewItemMonitorTypes;\n}\n\nexport interface AlbumsMonitoredResource {\n  albumIds?: number[] | null;\n  monitored?: boolean;\n}\n\nexport interface ArtistEditorResource {\n  artistIds?: number[] | null;\n  monitored?: boolean | null;\n  monitorNewItems?: NewItemMonitorTypes;\n  /** @format int32 */\n  qualityProfileId?: number | null;\n  /** @format int32 */\n  metadataProfileId?: number | null;\n  rootFolderPath?: string | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  moveFiles?: boolean;\n  deleteFiles?: boolean;\n  addImportListExclusion?: boolean;\n}\n\nexport interface ArtistResource {\n  /** @format int32 */\n  id?: number;\n  status?: ArtistStatusType;\n  ended?: boolean;\n  artistName?: string | null;\n  foreignArtistId?: string | null;\n  mbId?: string | null;\n  /** @format int32 */\n  tadbId?: number;\n  /** @format int32 */\n  discogsId?: number;\n  allMusicId?: string | null;\n  overview?: string | null;\n  artistType?: string | null;\n  disambiguation?: string | null;\n  links?: Links[] | null;\n  nextAlbum?: AlbumResource;\n  lastAlbum?: AlbumResource;\n  images?: MediaCover[] | null;\n  members?: Member[] | null;\n  remotePoster?: string | null;\n  path?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number;\n  /** @format int32 */\n  metadataProfileId?: number;\n  monitored?: boolean;\n  monitorNewItems?: NewItemMonitorTypes;\n  rootFolderPath?: string | null;\n  folder?: string | null;\n  genres?: string[] | null;\n  cleanName?: string | null;\n  sortName?: string | null;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  /** @format date-time */\n  added?: string;\n  addOptions?: AddArtistOptions;\n  ratings?: Ratings;\n  statistics?: ArtistStatisticsResource;\n}\n\nexport interface ArtistStatisticsResource {\n  /** @format int32 */\n  albumCount?: number;\n  /** @format int32 */\n  trackFileCount?: number;\n  /** @format int32 */\n  trackCount?: number;\n  /** @format int32 */\n  totalTrackCount?: number;\n  /** @format int64 */\n  sizeOnDisk?: number;\n  /** @format double */\n  percentOfTracks?: number;\n}\n\nexport interface ArtistTitleInfo {\n  title?: string | null;\n  titleWithoutYear?: string | null;\n  /** @format int32 */\n  year?: number;\n}\n\nexport interface AutoTaggingResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  removeTagsAutomatically?: boolean;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  specifications?: AutoTaggingSpecificationSchema[] | null;\n}\n\nexport interface AutoTaggingSpecificationSchema {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  implementation?: string | null;\n  implementationName?: string | null;\n  negate?: boolean;\n  required?: boolean;\n  fields?: Field[] | null;\n}\n\nexport interface BackupResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  path?: string | null;\n  type?: BackupType;\n  /** @format int64 */\n  size?: number;\n  /** @format date-time */\n  time?: string;\n}\n\nexport interface BlocklistBulkResource {\n  ids?: number[] | null;\n}\n\nexport interface BlocklistResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  artistId?: number;\n  albumIds?: number[] | null;\n  sourceTitle?: string | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format date-time */\n  date?: string;\n  protocol?: DownloadProtocol;\n  indexer?: string | null;\n  message?: string | null;\n  artist?: ArtistResource;\n}\n\nexport interface BlocklistResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: BlocklistResource[] | null;\n}\n\nexport interface Command {\n  sendUpdatesToClient?: boolean;\n  updateScheduledTask?: boolean;\n  completionMessage?: string | null;\n  requiresDiskAccess?: boolean;\n  isExclusive?: boolean;\n  isTypeExclusive?: boolean;\n  isLongRunning?: boolean;\n  name?: string | null;\n  /** @format date-time */\n  lastExecutionTime?: string | null;\n  /** @format date-time */\n  lastStartTime?: string | null;\n  trigger?: CommandTrigger;\n  suppressMessages?: boolean;\n  clientUserAgent?: string | null;\n}\n\nexport interface CommandResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  commandName?: string | null;\n  message?: string | null;\n  body?: Command;\n  priority?: CommandPriority;\n  status?: CommandStatus;\n  result?: CommandResult;\n  /** @format date-time */\n  queued?: string;\n  /** @format date-time */\n  started?: string | null;\n  /** @format date-time */\n  ended?: string | null;\n  /** @format date-span */\n  duration?: string | null;\n  exception?: string | null;\n  trigger?: CommandTrigger;\n  clientUserAgent?: string | null;\n  /** @format date-time */\n  stateChangeTime?: string | null;\n  sendUpdatesToClient?: boolean;\n  updateScheduledTask?: boolean;\n  /** @format date-time */\n  lastExecutionTime?: string | null;\n}\n\nexport interface CustomFilterResource {\n  /** @format int32 */\n  id?: number;\n  type?: string | null;\n  label?: string | null;\n  filters?: Record<string, any>[] | null;\n}\n\nexport interface CustomFormatBulkResource {\n  /** @uniqueItems true */\n  ids?: number[] | null;\n  includeCustomFormatWhenRenaming?: boolean | null;\n}\n\nexport interface CustomFormatResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  includeCustomFormatWhenRenaming?: boolean | null;\n  specifications?: CustomFormatSpecificationSchema[] | null;\n}\n\nexport interface CustomFormatSpecificationSchema {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  implementation?: string | null;\n  implementationName?: string | null;\n  infoLink?: string | null;\n  negate?: boolean;\n  required?: boolean;\n  fields?: Field[] | null;\n  presets?: CustomFormatSpecificationSchema[] | null;\n}\n\nexport interface DelayProfileResource {\n  /** @format int32 */\n  id?: number;\n  enableUsenet?: boolean;\n  enableTorrent?: boolean;\n  preferredProtocol?: DownloadProtocol;\n  /** @format int32 */\n  usenetDelay?: number;\n  /** @format int32 */\n  torrentDelay?: number;\n  bypassIfHighestQuality?: boolean;\n  bypassIfAboveCustomFormatScore?: boolean;\n  /** @format int32 */\n  minimumCustomFormatScore?: number;\n  /** @format int32 */\n  order?: number;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n}\n\nexport interface DiskSpaceResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  label?: string | null;\n  /** @format int64 */\n  freeSpace?: number;\n  /** @format int64 */\n  totalSpace?: number;\n}\n\nexport interface DownloadClientBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enable?: boolean | null;\n  /** @format int32 */\n  priority?: number | null;\n  removeCompletedDownloads?: boolean | null;\n  removeFailedDownloads?: boolean | null;\n}\n\nexport interface DownloadClientConfigResource {\n  /** @format int32 */\n  id?: number;\n  downloadClientWorkingFolders?: string | null;\n  enableCompletedDownloadHandling?: boolean;\n  autoRedownloadFailed?: boolean;\n  autoRedownloadFailedFromInteractiveSearch?: boolean;\n}\n\nexport interface DownloadClientResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: DownloadClientResource[] | null;\n  enable?: boolean;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  priority?: number;\n  removeCompletedDownloads?: boolean;\n  removeFailedDownloads?: boolean;\n}\n\nexport interface Field {\n  /** @format int32 */\n  order?: number;\n  name?: string | null;\n  label?: string | null;\n  unit?: string | null;\n  helpText?: string | null;\n  helpTextWarning?: string | null;\n  helpLink?: string | null;\n  value?: any;\n  type?: string | null;\n  advanced?: boolean;\n  selectOptions?: SelectOption[] | null;\n  selectOptionsProviderAction?: string | null;\n  section?: string | null;\n  hidden?: string | null;\n  privacy?: PrivacyLevel;\n  placeholder?: string | null;\n  isFloat?: boolean;\n}\n\nexport interface HealthResource {\n  /** @format int32 */\n  id?: number;\n  source?: string | null;\n  type?: HealthCheckResult;\n  message?: string | null;\n  wikiUrl?: string | null;\n}\n\nexport interface HistoryResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  albumId?: number;\n  /** @format int32 */\n  artistId?: number;\n  /** @format int32 */\n  trackId?: number;\n  sourceTitle?: string | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  qualityCutoffNotMet?: boolean;\n  /** @format date-time */\n  date?: string;\n  downloadId?: string | null;\n  eventType?: EntityHistoryEventType;\n  data?: Record<string, string | null>;\n  album?: AlbumResource;\n  artist?: ArtistResource;\n  track?: TrackResource;\n}\n\nexport interface HistoryResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: HistoryResource[] | null;\n}\n\nexport interface HostConfigResource {\n  /** @format int32 */\n  id?: number;\n  bindAddress?: string | null;\n  /** @format int32 */\n  port?: number;\n  /** @format int32 */\n  sslPort?: number;\n  enableSsl?: boolean;\n  launchBrowser?: boolean;\n  authenticationMethod?: AuthenticationType;\n  authenticationRequired?: AuthenticationRequiredType;\n  analyticsEnabled?: boolean;\n  username?: string | null;\n  password?: string | null;\n  passwordConfirmation?: string | null;\n  logLevel?: string | null;\n  /** @format int32 */\n  logSizeLimit?: number;\n  consoleLogLevel?: string | null;\n  branch?: string | null;\n  apiKey?: string | null;\n  sslCertPath?: string | null;\n  sslCertPassword?: string | null;\n  urlBase?: string | null;\n  instanceName?: string | null;\n  applicationUrl?: string | null;\n  updateAutomatically?: boolean;\n  updateMechanism?: UpdateMechanism;\n  updateScriptPath?: string | null;\n  proxyEnabled?: boolean;\n  proxyType?: ProxyType;\n  proxyHostname?: string | null;\n  /** @format int32 */\n  proxyPort?: number;\n  proxyUsername?: string | null;\n  proxyPassword?: string | null;\n  proxyBypassFilter?: string | null;\n  proxyBypassLocalAddresses?: boolean;\n  certificateValidation?: CertificateValidationType;\n  backupFolder?: string | null;\n  /** @format int32 */\n  backupInterval?: number;\n  /** @format int32 */\n  backupRetention?: number;\n  trustCgnatIpAddresses?: boolean;\n}\n\nexport interface ImportListBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enableAutomaticAdd?: boolean | null;\n  rootFolderPath?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number | null;\n}\n\nexport interface ImportListExclusionResource {\n  /** @format int32 */\n  id?: number;\n  foreignId?: string | null;\n  artistName?: string | null;\n}\n\nexport interface ImportListResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: ImportListResource[] | null;\n  enableAutomaticAdd?: boolean;\n  shouldMonitor?: ImportListMonitorType;\n  shouldMonitorExisting?: boolean;\n  shouldSearch?: boolean;\n  rootFolderPath?: string | null;\n  monitorNewItems?: NewItemMonitorTypes;\n  /** @format int32 */\n  qualityProfileId?: number;\n  /** @format int32 */\n  metadataProfileId?: number;\n  listType?: ImportListType;\n  /** @format int32 */\n  listOrder?: number;\n  /** @format date-span */\n  minRefreshInterval?: string;\n}\n\nexport interface IndexerBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enableRss?: boolean | null;\n  enableAutomaticSearch?: boolean | null;\n  enableInteractiveSearch?: boolean | null;\n  /** @format int32 */\n  priority?: number | null;\n}\n\nexport interface IndexerConfigResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  minimumAge?: number;\n  /** @format int32 */\n  maximumSize?: number;\n  /** @format int32 */\n  retention?: number;\n  /** @format int32 */\n  rssSyncInterval?: number;\n}\n\nexport interface IndexerFlagResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  nameLower?: string | null;\n}\n\nexport interface IndexerResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: IndexerResource[] | null;\n  enableRss?: boolean;\n  enableAutomaticSearch?: boolean;\n  enableInteractiveSearch?: boolean;\n  supportsRss?: boolean;\n  supportsSearch?: boolean;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  priority?: number;\n  /** @format int32 */\n  downloadClientId?: number;\n}\n\nexport interface IsoCountry {\n  twoLetterCode?: string | null;\n  name?: string | null;\n}\n\nexport interface LanguageResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  nameLower?: string | null;\n}\n\nexport interface Links {\n  url?: string | null;\n  name?: string | null;\n}\n\nexport interface LocalizationResource {\n  /** @format int32 */\n  id?: number;\n  strings?: Record<string, string | null>;\n}\n\nexport interface LogFileResource {\n  /** @format int32 */\n  id?: number;\n  filename?: string | null;\n  /** @format date-time */\n  lastWriteTime?: string;\n  contentsUrl?: string | null;\n  downloadUrl?: string | null;\n}\n\nexport interface LogResource {\n  /** @format int32 */\n  id?: number;\n  /** @format date-time */\n  time?: string;\n  exception?: string | null;\n  exceptionType?: string | null;\n  level?: string | null;\n  logger?: string | null;\n  message?: string | null;\n  method?: string | null;\n}\n\nexport interface LogResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: LogResource[] | null;\n}\n\nexport interface ManualImportResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  name?: string | null;\n  /** @format int64 */\n  size?: number;\n  artist?: ArtistResource;\n  album?: AlbumResource;\n  /** @format int32 */\n  albumReleaseId?: number;\n  tracks?: TrackResource[] | null;\n  quality?: QualityModel;\n  releaseGroup?: string | null;\n  /** @format int32 */\n  qualityWeight?: number;\n  downloadId?: string | null;\n  /** @format int32 */\n  indexerFlags?: number;\n  rejections?: Rejection[] | null;\n  audioTags?: ParsedTrackInfo;\n  additionalFile?: boolean;\n  replaceExistingFiles?: boolean;\n  disableReleaseSwitching?: boolean;\n}\n\nexport interface ManualImportUpdateResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  name?: string | null;\n  /** @format int32 */\n  artistId?: number | null;\n  /** @format int32 */\n  albumId?: number | null;\n  /** @format int32 */\n  albumReleaseId?: number | null;\n  tracks?: TrackResource[] | null;\n  trackIds?: number[] | null;\n  quality?: QualityModel;\n  releaseGroup?: string | null;\n  /** @format int32 */\n  indexerFlags?: number;\n  downloadId?: string | null;\n  additionalFile?: boolean;\n  replaceExistingFiles?: boolean;\n  disableReleaseSwitching?: boolean;\n  rejections?: Rejection[] | null;\n}\n\nexport interface MediaCover {\n  url?: string | null;\n  coverType?: MediaCoverTypes;\n  extension?: string | null;\n  remoteUrl?: string | null;\n}\n\nexport interface MediaInfoModel {\n  audioFormat?: string | null;\n  /** @format int32 */\n  audioBitrate?: number;\n  /** @format int32 */\n  audioChannels?: number;\n  /** @format int32 */\n  audioBits?: number;\n  /** @format int32 */\n  audioSampleRate?: number;\n}\n\nexport interface MediaInfoResource {\n  /** @format int32 */\n  id?: number;\n  /** @format double */\n  audioChannels?: number;\n  audioBitRate?: string | null;\n  audioCodec?: string | null;\n  audioBits?: string | null;\n  audioSampleRate?: string | null;\n}\n\nexport interface MediaManagementConfigResource {\n  /** @format int32 */\n  id?: number;\n  autoUnmonitorPreviouslyDownloadedTracks?: boolean;\n  recycleBin?: string | null;\n  /** @format int32 */\n  recycleBinCleanupDays?: number;\n  downloadPropersAndRepacks?: ProperDownloadTypes;\n  createEmptyArtistFolders?: boolean;\n  deleteEmptyFolders?: boolean;\n  fileDate?: FileDateType;\n  watchLibraryForChanges?: boolean;\n  rescanAfterRefresh?: RescanAfterRefreshType;\n  allowFingerprinting?: AllowFingerprinting;\n  setPermissionsLinux?: boolean;\n  chmodFolder?: string | null;\n  chownGroup?: string | null;\n  skipFreeSpaceCheckWhenImporting?: boolean;\n  /** @format int32 */\n  minimumFreeSpaceWhenImporting?: number;\n  copyUsingHardlinks?: boolean;\n  enableMediaInfo?: boolean;\n  useScriptImport?: boolean;\n  scriptImportPath?: string | null;\n  importExtraFiles?: boolean;\n  extraFileExtensions?: string | null;\n}\n\nexport interface MediumResource {\n  /** @format int32 */\n  mediumNumber?: number;\n  mediumName?: string | null;\n  mediumFormat?: string | null;\n}\n\nexport interface Member {\n  name?: string | null;\n  instrument?: string | null;\n  images?: MediaCover[] | null;\n}\n\nexport interface MetadataProfileResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  primaryAlbumTypes?: ProfilePrimaryAlbumTypeItemResource[] | null;\n  secondaryAlbumTypes?: ProfileSecondaryAlbumTypeItemResource[] | null;\n  releaseStatuses?: ProfileReleaseStatusItemResource[] | null;\n}\n\nexport interface MetadataProviderConfigResource {\n  /** @format int32 */\n  id?: number;\n  metadataSource?: string | null;\n  writeAudioTags?: WriteAudioTagsType;\n  scrubAudioTags?: boolean;\n  embedCoverArt?: boolean;\n}\n\nexport interface MetadataResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: MetadataResource[] | null;\n  enable?: boolean;\n}\n\nexport interface MonitoringOptions {\n  monitor?: MonitorTypes;\n  albumsToMonitor?: string[] | null;\n  monitored?: boolean;\n}\n\nexport interface NamingConfigResource {\n  /** @format int32 */\n  id?: number;\n  renameTracks?: boolean;\n  replaceIllegalCharacters?: boolean;\n  /** @format int32 */\n  colonReplacementFormat?: number;\n  standardTrackFormat?: string | null;\n  multiDiscTrackFormat?: string | null;\n  artistFolderFormat?: string | null;\n  includeArtistName?: boolean;\n  includeAlbumTitle?: boolean;\n  includeQuality?: boolean;\n  replaceSpaces?: boolean;\n  separator?: string | null;\n  numberStyle?: string | null;\n}\n\nexport interface NotificationResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: NotificationResource[] | null;\n  link?: string | null;\n  onGrab?: boolean;\n  onReleaseImport?: boolean;\n  onUpgrade?: boolean;\n  onRename?: boolean;\n  onArtistAdd?: boolean;\n  onArtistDelete?: boolean;\n  onAlbumDelete?: boolean;\n  onHealthIssue?: boolean;\n  onHealthRestored?: boolean;\n  onDownloadFailure?: boolean;\n  onImportFailure?: boolean;\n  onTrackRetag?: boolean;\n  onApplicationUpdate?: boolean;\n  supportsOnGrab?: boolean;\n  supportsOnReleaseImport?: boolean;\n  supportsOnUpgrade?: boolean;\n  supportsOnRename?: boolean;\n  supportsOnArtistAdd?: boolean;\n  supportsOnArtistDelete?: boolean;\n  supportsOnAlbumDelete?: boolean;\n  supportsOnHealthIssue?: boolean;\n  supportsOnHealthRestored?: boolean;\n  includeHealthWarnings?: boolean;\n  supportsOnDownloadFailure?: boolean;\n  supportsOnImportFailure?: boolean;\n  supportsOnTrackRetag?: boolean;\n  supportsOnApplicationUpdate?: boolean;\n  testCommand?: string | null;\n}\n\nexport interface ParseResource {\n  /** @format int32 */\n  id?: number;\n  title?: string | null;\n  parsedAlbumInfo?: ParsedAlbumInfo;\n  artist?: ArtistResource;\n  albums?: AlbumResource[] | null;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n}\n\nexport interface ParsedAlbumInfo {\n  releaseTitle?: string | null;\n  albumTitle?: string | null;\n  artistName?: string | null;\n  albumType?: string | null;\n  artistTitleInfo?: ArtistTitleInfo;\n  quality?: QualityModel;\n  releaseDate?: string | null;\n  discography?: boolean;\n  /** @format int32 */\n  discographyStart?: number;\n  /** @format int32 */\n  discographyEnd?: number;\n  releaseGroup?: string | null;\n  releaseHash?: string | null;\n  releaseVersion?: string | null;\n}\n\nexport interface ParsedTrackInfo {\n  title?: string | null;\n  cleanTitle?: string | null;\n  artistTitle?: string | null;\n  albumTitle?: string | null;\n  artistTitleInfo?: ArtistTitleInfo;\n  artistMBId?: string | null;\n  albumMBId?: string | null;\n  releaseMBId?: string | null;\n  recordingMBId?: string | null;\n  trackMBId?: string | null;\n  /** @format int32 */\n  discNumber?: number;\n  /** @format int32 */\n  discCount?: number;\n  country?: IsoCountry;\n  /** @format int32 */\n  year?: number;\n  label?: string | null;\n  catalogNumber?: string | null;\n  disambiguation?: string | null;\n  /** @format date-span */\n  duration?: string;\n  quality?: QualityModel;\n  mediaInfo?: MediaInfoModel;\n  trackNumbers?: number[] | null;\n  releaseGroup?: string | null;\n  releaseHash?: string | null;\n}\n\nexport interface PingResource {\n  status?: string | null;\n}\n\nexport interface PrimaryAlbumType {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n}\n\nexport interface ProfileFormatItemResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  format?: number;\n  name?: string | null;\n  /** @format int32 */\n  score?: number;\n}\n\nexport interface ProfilePrimaryAlbumTypeItemResource {\n  /** @format int32 */\n  id?: number;\n  albumType?: PrimaryAlbumType;\n  allowed?: boolean;\n}\n\nexport interface ProfileReleaseStatusItemResource {\n  /** @format int32 */\n  id?: number;\n  releaseStatus?: ReleaseStatus;\n  allowed?: boolean;\n}\n\nexport interface ProfileSecondaryAlbumTypeItemResource {\n  /** @format int32 */\n  id?: number;\n  albumType?: SecondaryAlbumType;\n  allowed?: boolean;\n}\n\nexport interface ProviderMessage {\n  message?: string | null;\n  type?: ProviderMessageType;\n}\n\nexport interface Quality {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n}\n\nexport interface QualityDefinitionResource {\n  /** @format int32 */\n  id?: number;\n  quality?: Quality;\n  title?: string | null;\n  /** @format int32 */\n  weight?: number;\n  /** @format double */\n  minSize?: number | null;\n  /** @format double */\n  maxSize?: number | null;\n  /** @format double */\n  preferredSize?: number | null;\n}\n\nexport interface QualityModel {\n  quality?: Quality;\n  revision?: Revision;\n}\n\nexport interface QualityProfileQualityItemResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  quality?: Quality;\n  items?: QualityProfileQualityItemResource[] | null;\n  allowed?: boolean;\n}\n\nexport interface QualityProfileResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  upgradeAllowed?: boolean;\n  /** @format int32 */\n  cutoff?: number;\n  items?: QualityProfileQualityItemResource[] | null;\n  /** @format int32 */\n  minFormatScore?: number;\n  /** @format int32 */\n  cutoffFormatScore?: number;\n  formatItems?: ProfileFormatItemResource[] | null;\n}\n\nexport interface QueueBulkResource {\n  ids?: number[] | null;\n}\n\nexport interface QueueResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  artistId?: number | null;\n  /** @format int32 */\n  albumId?: number | null;\n  artist?: ArtistResource;\n  album?: AlbumResource;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  /** @format double */\n  size?: number;\n  title?: string | null;\n  /** @format double */\n  sizeleft?: number;\n  /** @format date-span */\n  timeleft?: string | null;\n  /** @format date-time */\n  estimatedCompletionTime?: string | null;\n  /** @format date-time */\n  added?: string | null;\n  status?: string | null;\n  trackedDownloadStatus?: TrackedDownloadStatus;\n  trackedDownloadState?: TrackedDownloadState;\n  statusMessages?: TrackedDownloadStatusMessage[] | null;\n  errorMessage?: string | null;\n  downloadId?: string | null;\n  protocol?: DownloadProtocol;\n  downloadClient?: string | null;\n  downloadClientHasPostImportCategory?: boolean;\n  indexer?: string | null;\n  outputPath?: string | null;\n  /** @format int32 */\n  trackFileCount?: number;\n  /** @format int32 */\n  trackHasFileCount?: number;\n  downloadForced?: boolean;\n}\n\nexport interface QueueResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: QueueResource[] | null;\n}\n\nexport interface QueueStatusResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  totalCount?: number;\n  /** @format int32 */\n  count?: number;\n  /** @format int32 */\n  unknownCount?: number;\n  errors?: boolean;\n  warnings?: boolean;\n  unknownErrors?: boolean;\n  unknownWarnings?: boolean;\n}\n\nexport interface Ratings {\n  /** @format int32 */\n  votes?: number;\n  /** @format double */\n  value?: number;\n}\n\nexport interface Rejection {\n  reason?: string | null;\n  type?: RejectionType;\n}\n\nexport interface ReleaseProfileResource {\n  /** @format int32 */\n  id?: number;\n  enabled?: boolean;\n  required?: string[] | null;\n  ignored?: string[] | null;\n  /** @format int32 */\n  indexerId?: number;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n}\n\nexport interface ReleaseResource {\n  /** @format int32 */\n  id?: number;\n  guid?: string | null;\n  quality?: QualityModel;\n  /** @format int32 */\n  qualityWeight?: number;\n  /** @format int32 */\n  age?: number;\n  /** @format double */\n  ageHours?: number;\n  /** @format double */\n  ageMinutes?: number;\n  /** @format int64 */\n  size?: number;\n  /** @format int32 */\n  indexerId?: number;\n  indexer?: string | null;\n  releaseGroup?: string | null;\n  subGroup?: string | null;\n  releaseHash?: string | null;\n  title?: string | null;\n  discography?: boolean;\n  sceneSource?: boolean;\n  airDate?: string | null;\n  artistName?: string | null;\n  albumTitle?: string | null;\n  approved?: boolean;\n  temporarilyRejected?: boolean;\n  rejected?: boolean;\n  rejections?: string[] | null;\n  /** @format date-time */\n  publishDate?: string;\n  commentUrl?: string | null;\n  downloadUrl?: string | null;\n  infoUrl?: string | null;\n  downloadAllowed?: boolean;\n  /** @format int32 */\n  releaseWeight?: number;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  magnetUrl?: string | null;\n  infoHash?: string | null;\n  /** @format int32 */\n  seeders?: number | null;\n  /** @format int32 */\n  leechers?: number | null;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  indexerFlags?: number;\n  /** @format int32 */\n  artistId?: number | null;\n  /** @format int32 */\n  albumId?: number | null;\n  /** @format int32 */\n  downloadClientId?: number | null;\n  downloadClient?: string | null;\n}\n\nexport interface ReleaseStatus {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n}\n\nexport interface RemotePathMappingResource {\n  /** @format int32 */\n  id?: number;\n  host?: string | null;\n  remotePath?: string | null;\n  localPath?: string | null;\n}\n\nexport interface RenameTrackResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  artistId?: number;\n  /** @format int32 */\n  albumId?: number;\n  trackNumbers?: number[] | null;\n  /** @format int32 */\n  trackFileId?: number;\n  existingPath?: string | null;\n  newPath?: string | null;\n}\n\nexport interface RetagTrackResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  artistId?: number;\n  /** @format int32 */\n  albumId?: number;\n  trackNumbers?: number[] | null;\n  /** @format int32 */\n  trackFileId?: number;\n  path?: string | null;\n  changes?: TagDifference[] | null;\n}\n\nexport interface Revision {\n  /** @format int32 */\n  version?: number;\n  /** @format int32 */\n  real?: number;\n  isRepack?: boolean;\n}\n\nexport interface RootFolderResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  path?: string | null;\n  /** @format int32 */\n  defaultMetadataProfileId?: number;\n  /** @format int32 */\n  defaultQualityProfileId?: number;\n  defaultMonitorOption?: MonitorTypes;\n  defaultNewItemMonitorOption?: NewItemMonitorTypes;\n  /** @uniqueItems true */\n  defaultTags?: number[] | null;\n  accessible?: boolean;\n  /** @format int64 */\n  freeSpace?: number | null;\n  /** @format int64 */\n  totalSpace?: number | null;\n}\n\nexport interface SearchResource {\n  /** @format int32 */\n  id?: number;\n  foreignId?: string | null;\n  artist?: ArtistResource;\n  album?: AlbumResource;\n}\n\nexport interface SecondaryAlbumType {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n}\n\nexport interface SelectOption {\n  /** @format int32 */\n  value?: number;\n  name?: string | null;\n  /** @format int32 */\n  order?: number;\n  hint?: string | null;\n}\n\nexport interface SystemResource {\n  appName?: string | null;\n  instanceName?: string | null;\n  version?: string | null;\n  /** @format date-time */\n  buildTime?: string;\n  isDebug?: boolean;\n  isProduction?: boolean;\n  isAdmin?: boolean;\n  isUserInteractive?: boolean;\n  startupPath?: string | null;\n  appData?: string | null;\n  osName?: string | null;\n  osVersion?: string | null;\n  isNetCore?: boolean;\n  isLinux?: boolean;\n  isOsx?: boolean;\n  isWindows?: boolean;\n  isDocker?: boolean;\n  mode?: RuntimeMode;\n  branch?: string | null;\n  databaseType?: DatabaseType;\n  databaseVersion?: string | null;\n  authentication?: AuthenticationType;\n  /** @format int32 */\n  migrationVersion?: number;\n  urlBase?: string | null;\n  runtimeVersion?: string | null;\n  runtimeName?: string | null;\n  /** @format date-time */\n  startTime?: string;\n  packageVersion?: string | null;\n  packageAuthor?: string | null;\n  packageUpdateMechanism?: UpdateMechanism;\n  packageUpdateMechanismMessage?: string | null;\n}\n\nexport interface TagDetailsResource {\n  /** @format int32 */\n  id?: number;\n  label?: string | null;\n  delayProfileIds?: number[] | null;\n  importListIds?: number[] | null;\n  notificationIds?: number[] | null;\n  restrictionIds?: number[] | null;\n  indexerIds?: number[] | null;\n  downloadClientIds?: number[] | null;\n  autoTagIds?: number[] | null;\n  artistIds?: number[] | null;\n}\n\nexport interface TagDifference {\n  field?: string | null;\n  oldValue?: string | null;\n  newValue?: string | null;\n}\n\nexport interface TagResource {\n  /** @format int32 */\n  id?: number;\n  label?: string | null;\n}\n\nexport interface TaskResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  taskName?: string | null;\n  /** @format int32 */\n  interval?: number;\n  /** @format date-time */\n  lastExecution?: string;\n  /** @format date-time */\n  lastStartTime?: string;\n  /** @format date-time */\n  nextExecution?: string;\n  /** @format date-span */\n  lastDuration?: string;\n}\n\nexport interface TrackFileListResource {\n  trackFileIds?: number[] | null;\n  quality?: QualityModel;\n  sceneName?: string | null;\n  releaseGroup?: string | null;\n}\n\nexport interface TrackFileResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  artistId?: number;\n  /** @format int32 */\n  albumId?: number;\n  path?: string | null;\n  /** @format int64 */\n  size?: number;\n  /** @format date-time */\n  dateAdded?: string;\n  sceneName?: string | null;\n  releaseGroup?: string | null;\n  quality?: QualityModel;\n  /** @format int32 */\n  qualityWeight?: number;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  /** @format int32 */\n  indexerFlags?: number | null;\n  mediaInfo?: MediaInfoResource;\n  qualityCutoffNotMet?: boolean;\n  audioTags?: ParsedTrackInfo;\n}\n\nexport interface TrackResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  artistId?: number;\n  foreignTrackId?: string | null;\n  foreignRecordingId?: string | null;\n  /** @format int32 */\n  trackFileId?: number;\n  /** @format int32 */\n  albumId?: number;\n  explicit?: boolean;\n  /** @format int32 */\n  absoluteTrackNumber?: number;\n  trackNumber?: string | null;\n  title?: string | null;\n  /** @format int32 */\n  duration?: number;\n  trackFile?: TrackFileResource;\n  /** @format int32 */\n  mediumNumber?: number;\n  hasFile?: boolean;\n  artist?: ArtistResource;\n  ratings?: Ratings;\n}\n\nexport interface TrackedDownloadStatusMessage {\n  title?: string | null;\n  messages?: string[] | null;\n}\n\nexport interface UiConfigResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  firstDayOfWeek?: number;\n  calendarWeekColumnHeader?: string | null;\n  shortDateFormat?: string | null;\n  longDateFormat?: string | null;\n  timeFormat?: string | null;\n  showRelativeDates?: boolean;\n  enableColorImpairedMode?: boolean;\n  /** @format int32 */\n  uiLanguage?: number;\n  expandAlbumByDefault?: boolean;\n  expandSingleByDefault?: boolean;\n  expandEPByDefault?: boolean;\n  expandBroadcastByDefault?: boolean;\n  expandOtherByDefault?: boolean;\n  theme?: string | null;\n}\n\nexport interface UpdateChanges {\n  new?: string[] | null;\n  fixed?: string[] | null;\n}\n\nexport interface UpdateResource {\n  /** @format int32 */\n  id?: number;\n  version?: string | null;\n  branch?: string | null;\n  /** @format date-time */\n  releaseDate?: string;\n  fileName?: string | null;\n  url?: string | null;\n  installed?: boolean;\n  /** @format date-time */\n  installedOn?: string | null;\n  installable?: boolean;\n  latest?: boolean;\n  changes?: UpdateChanges;\n  hash?: string | null;\n}\n"
  },
  {
    "path": "src/__generated__/radarr/Api.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { ContentType, HttpClient, RequestParams } from \"./../../ky-client\";\nimport {\n  AlternativeTitleResource,\n  ApiInfoResource,\n  AutoTaggingResource,\n  BackupResource,\n  BlocklistBulkResource,\n  BlocklistResource,\n  BlocklistResourcePagingResource,\n  CollectionResource,\n  CollectionUpdateResource,\n  ColonReplacementFormat,\n  CommandResource,\n  CreditResource,\n  CustomFilterResource,\n  CustomFormatBulkResource,\n  CustomFormatResource,\n  DelayProfileResource,\n  DiskSpaceResource,\n  DownloadClientBulkResource,\n  DownloadClientConfigResource,\n  DownloadClientResource,\n  DownloadProtocol,\n  ExtraFileResource,\n  HealthResource,\n  HistoryResource,\n  HistoryResourcePagingResource,\n  HostConfigResource,\n  ImportListBulkResource,\n  ImportListConfigResource,\n  ImportListExclusionBulkResource,\n  ImportListExclusionResource,\n  ImportListExclusionResourcePagingResource,\n  ImportListResource,\n  IndexerBulkResource,\n  IndexerConfigResource,\n  IndexerFlagResource,\n  IndexerResource,\n  LanguageResource,\n  LocalizationLanguageResource,\n  LogFileResource,\n  LogResourcePagingResource,\n  ManualImportReprocessResource,\n  ManualImportResource,\n  MediaManagementConfigResource,\n  MetadataConfigResource,\n  MetadataResource,\n  MovieEditorResource,\n  MovieFileListResource,\n  MovieFileResource,\n  MovieHistoryEventType,\n  MovieResource,\n  MovieResourcePagingResource,\n  NamingConfigResource,\n  NotificationResource,\n  ParseResource,\n  QualityDefinitionLimitsResource,\n  QualityDefinitionResource,\n  QualityProfileResource,\n  QueueBulkResource,\n  QueueResource,\n  QueueResourcePagingResource,\n  QueueStatus,\n  QueueStatusResource,\n  ReleaseProfileResource,\n  ReleaseResource,\n  RemotePathMappingResource,\n  RenameMovieResource,\n  RootFolderResource,\n  SortDirection,\n  SystemResource,\n  TagDetailsResource,\n  TagResource,\n  TaskResource,\n  UiConfigResource,\n  UpdateResource,\n} from \"./data-contracts\";\n\nexport class Api<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags AlternativeTitle\n   * @name V3AlttitleList\n   * @request GET:/api/v3/alttitle\n   * @secure\n   */\n  v3AlttitleList = (\n    query?: {\n      /** @format int32 */\n      movieId?: number;\n      /** @format int32 */\n      movieMetadataId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<AlternativeTitleResource[], any>({\n      path: `/api/v3/alttitle`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AlternativeTitle\n   * @name V3AlttitleDetail\n   * @request GET:/api/v3/alttitle/{id}\n   * @secure\n   */\n  v3AlttitleDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<AlternativeTitleResource, any>({\n      path: `/api/v3/alttitle/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ApiInfo\n   * @name GetApi\n   * @request GET:/api\n   * @secure\n   */\n  getApi = (params: RequestParams = {}) =>\n    this.http.request<ApiInfoResource, any>({\n      path: `/api`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingCreate\n   * @request POST:/api/v3/autotagging\n   * @secure\n   */\n  v3AutotaggingCreate = (data: AutoTaggingResource, params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource, any>({\n      path: `/api/v3/autotagging`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingList\n   * @request GET:/api/v3/autotagging\n   * @secure\n   */\n  v3AutotaggingList = (params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource[], any>({\n      path: `/api/v3/autotagging`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingUpdate\n   * @request PUT:/api/v3/autotagging/{id}\n   * @secure\n   */\n  v3AutotaggingUpdate = (id: string, data: AutoTaggingResource, params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource, any>({\n      path: `/api/v3/autotagging/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingDelete\n   * @request DELETE:/api/v3/autotagging/{id}\n   * @secure\n   */\n  v3AutotaggingDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/autotagging/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingDetail\n   * @request GET:/api/v3/autotagging/{id}\n   * @secure\n   */\n  v3AutotaggingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource, any>({\n      path: `/api/v3/autotagging/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingSchemaList\n   * @request GET:/api/v3/autotagging/schema\n   * @secure\n   */\n  v3AutotaggingSchemaList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/autotagging/schema`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V3SystemBackupList\n   * @request GET:/api/v3/system/backup\n   * @secure\n   */\n  v3SystemBackupList = (params: RequestParams = {}) =>\n    this.http.request<BackupResource[], any>({\n      path: `/api/v3/system/backup`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V3SystemBackupDelete\n   * @request DELETE:/api/v3/system/backup/{id}\n   * @secure\n   */\n  v3SystemBackupDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/backup/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V3SystemBackupRestoreCreate\n   * @request POST:/api/v3/system/backup/restore/{id}\n   * @secure\n   */\n  v3SystemBackupRestoreCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/backup/restore/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V3SystemBackupRestoreUploadCreate\n   * @request POST:/api/v3/system/backup/restore/upload\n   * @secure\n   */\n  v3SystemBackupRestoreUploadCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/backup/restore/upload`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V3BlocklistList\n   * @request GET:/api/v3/blocklist\n   * @secure\n   */\n  v3BlocklistList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      movieIds?: number[];\n      protocols?: DownloadProtocol[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<BlocklistResourcePagingResource, any>({\n      path: `/api/v3/blocklist`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V3BlocklistMovieList\n   * @request GET:/api/v3/blocklist/movie\n   * @secure\n   */\n  v3BlocklistMovieList = (\n    query?: {\n      /** @format int32 */\n      movieId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<BlocklistResource[], any>({\n      path: `/api/v3/blocklist/movie`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V3BlocklistDelete\n   * @request DELETE:/api/v3/blocklist/{id}\n   * @secure\n   */\n  v3BlocklistDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/blocklist/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V3BlocklistBulkDelete\n   * @request DELETE:/api/v3/blocklist/bulk\n   * @secure\n   */\n  v3BlocklistBulkDelete = (data: BlocklistBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/blocklist/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Calendar\n   * @name V3CalendarList\n   * @request GET:/api/v3/calendar\n   * @secure\n   */\n  v3CalendarList = (\n    query?: {\n      /** @format date-time */\n      start?: string;\n      /** @format date-time */\n      end?: string;\n      /** @default false */\n      unmonitored?: boolean;\n      /** @default \"\" */\n      tags?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MovieResource[], any>({\n      path: `/api/v3/calendar`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Collection\n   * @name V3CollectionList\n   * @request GET:/api/v3/collection\n   * @secure\n   */\n  v3CollectionList = (\n    query?: {\n      /** @format int32 */\n      tmdbId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<CollectionResource[], any>({\n      path: `/api/v3/collection`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Collection\n   * @name V3CollectionUpdate\n   * @request PUT:/api/v3/collection\n   * @secure\n   */\n  v3CollectionUpdate = (data: CollectionUpdateResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/collection`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Collection\n   * @name V3CollectionUpdate2\n   * @request PUT:/api/v3/collection/{id}\n   * @originalName v3CollectionUpdate\n   * @duplicate\n   * @secure\n   */\n  v3CollectionUpdate2 = (id: string, data: CollectionResource, params: RequestParams = {}) =>\n    this.http.request<CollectionResource, any>({\n      path: `/api/v3/collection/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Collection\n   * @name V3CollectionDetail\n   * @request GET:/api/v3/collection/{id}\n   * @secure\n   */\n  v3CollectionDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CollectionResource, any>({\n      path: `/api/v3/collection/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V3CommandCreate\n   * @request POST:/api/v3/command\n   * @secure\n   */\n  v3CommandCreate = (data: CommandResource, params: RequestParams = {}) =>\n    this.http.request<CommandResource, any>({\n      path: `/api/v3/command`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V3CommandList\n   * @request GET:/api/v3/command\n   * @secure\n   */\n  v3CommandList = (params: RequestParams = {}) =>\n    this.http.request<CommandResource[], any>({\n      path: `/api/v3/command`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V3CommandDelete\n   * @request DELETE:/api/v3/command/{id}\n   * @secure\n   */\n  v3CommandDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/command/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V3CommandDetail\n   * @request GET:/api/v3/command/{id}\n   * @secure\n   */\n  v3CommandDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CommandResource, any>({\n      path: `/api/v3/command/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Credit\n   * @name V3CreditList\n   * @request GET:/api/v3/credit\n   * @secure\n   */\n  v3CreditList = (\n    query?: {\n      /** @format int32 */\n      movieId?: number;\n      /** @format int32 */\n      movieMetadataId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/credit`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Credit\n   * @name V3CreditDetail\n   * @request GET:/api/v3/credit/{id}\n   * @secure\n   */\n  v3CreditDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CreditResource, any>({\n      path: `/api/v3/credit/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterList\n   * @request GET:/api/v3/customfilter\n   * @secure\n   */\n  v3CustomfilterList = (params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource[], any>({\n      path: `/api/v3/customfilter`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterCreate\n   * @request POST:/api/v3/customfilter\n   * @secure\n   */\n  v3CustomfilterCreate = (data: CustomFilterResource, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v3/customfilter`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterUpdate\n   * @request PUT:/api/v3/customfilter/{id}\n   * @secure\n   */\n  v3CustomfilterUpdate = (id: string, data: CustomFilterResource, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v3/customfilter/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterDelete\n   * @request DELETE:/api/v3/customfilter/{id}\n   * @secure\n   */\n  v3CustomfilterDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/customfilter/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterDetail\n   * @request GET:/api/v3/customfilter/{id}\n   * @secure\n   */\n  v3CustomfilterDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v3/customfilter/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatList\n   * @request GET:/api/v3/customformat\n   * @secure\n   */\n  v3CustomformatList = (params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource[], any>({\n      path: `/api/v3/customformat`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatCreate\n   * @request POST:/api/v3/customformat\n   * @secure\n   */\n  v3CustomformatCreate = (data: CustomFormatResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v3/customformat`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatUpdate\n   * @request PUT:/api/v3/customformat/{id}\n   * @secure\n   */\n  v3CustomformatUpdate = (id: string, data: CustomFormatResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v3/customformat/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatDelete\n   * @request DELETE:/api/v3/customformat/{id}\n   * @secure\n   */\n  v3CustomformatDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/customformat/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatDetail\n   * @request GET:/api/v3/customformat/{id}\n   * @secure\n   */\n  v3CustomformatDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v3/customformat/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatBulkUpdate\n   * @request PUT:/api/v3/customformat/bulk\n   * @secure\n   */\n  v3CustomformatBulkUpdate = (data: CustomFormatBulkResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v3/customformat/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatBulkDelete\n   * @request DELETE:/api/v3/customformat/bulk\n   * @secure\n   */\n  v3CustomformatBulkDelete = (data: CustomFormatBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/customformat/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatSchemaList\n   * @request GET:/api/v3/customformat/schema\n   * @secure\n   */\n  v3CustomformatSchemaList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/customformat/schema`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Cutoff\n   * @name V3WantedCutoffList\n   * @request GET:/api/v3/wanted/cutoff\n   * @secure\n   */\n  v3WantedCutoffList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default true */\n      monitored?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MovieResourcePagingResource, any>({\n      path: `/api/v3/wanted/cutoff`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileCreate\n   * @request POST:/api/v3/delayprofile\n   * @secure\n   */\n  v3DelayprofileCreate = (data: DelayProfileResource, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v3/delayprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileList\n   * @request GET:/api/v3/delayprofile\n   * @secure\n   */\n  v3DelayprofileList = (params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource[], any>({\n      path: `/api/v3/delayprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileDelete\n   * @request DELETE:/api/v3/delayprofile/{id}\n   * @secure\n   */\n  v3DelayprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/delayprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileUpdate\n   * @request PUT:/api/v3/delayprofile/{id}\n   * @secure\n   */\n  v3DelayprofileUpdate = (id: string, data: DelayProfileResource, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v3/delayprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileDetail\n   * @request GET:/api/v3/delayprofile/{id}\n   * @secure\n   */\n  v3DelayprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v3/delayprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileReorderUpdate\n   * @request PUT:/api/v3/delayprofile/reorder/{id}\n   * @secure\n   */\n  v3DelayprofileReorderUpdate = (\n    id: number,\n    query?: {\n      /** @format int32 */\n      after?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DelayProfileResource[], any>({\n      path: `/api/v3/delayprofile/reorder/${id}`,\n      method: \"PUT\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DiskSpace\n   * @name V3DiskspaceList\n   * @request GET:/api/v3/diskspace\n   * @secure\n   */\n  v3DiskspaceList = (params: RequestParams = {}) =>\n    this.http.request<DiskSpaceResource[], any>({\n      path: `/api/v3/diskspace`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientList\n   * @request GET:/api/v3/downloadclient\n   * @secure\n   */\n  v3DownloadclientList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource[], any>({\n      path: `/api/v3/downloadclient`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientCreate\n   * @request POST:/api/v3/downloadclient\n   * @secure\n   */\n  v3DownloadclientCreate = (\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v3/downloadclient`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientUpdate\n   * @request PUT:/api/v3/downloadclient/{id}\n   * @secure\n   */\n  v3DownloadclientUpdate = (\n    id: number,\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v3/downloadclient/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientDelete\n   * @request DELETE:/api/v3/downloadclient/{id}\n   * @secure\n   */\n  v3DownloadclientDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientDetail\n   * @request GET:/api/v3/downloadclient/{id}\n   * @secure\n   */\n  v3DownloadclientDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v3/downloadclient/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientBulkUpdate\n   * @request PUT:/api/v3/downloadclient/bulk\n   * @secure\n   */\n  v3DownloadclientBulkUpdate = (data: DownloadClientBulkResource, params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v3/downloadclient/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientBulkDelete\n   * @request DELETE:/api/v3/downloadclient/bulk\n   * @secure\n   */\n  v3DownloadclientBulkDelete = (data: DownloadClientBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientSchemaList\n   * @request GET:/api/v3/downloadclient/schema\n   * @secure\n   */\n  v3DownloadclientSchemaList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource[], any>({\n      path: `/api/v3/downloadclient/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientTestCreate\n   * @request POST:/api/v3/downloadclient/test\n   * @secure\n   */\n  v3DownloadclientTestCreate = (\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientTestallCreate\n   * @request POST:/api/v3/downloadclient/testall\n   * @secure\n   */\n  v3DownloadclientTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientActionCreate\n   * @request POST:/api/v3/downloadclient/action/{name}\n   * @secure\n   */\n  v3DownloadclientActionCreate = (name: string, data: DownloadClientResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V3ConfigDownloadclientList\n   * @request GET:/api/v3/config/downloadclient\n   * @secure\n   */\n  v3ConfigDownloadclientList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v3/config/downloadclient`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V3ConfigDownloadclientUpdate\n   * @request PUT:/api/v3/config/downloadclient/{id}\n   * @secure\n   */\n  v3ConfigDownloadclientUpdate = (id: string, data: DownloadClientConfigResource, params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v3/config/downloadclient/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V3ConfigDownloadclientDetail\n   * @request GET:/api/v3/config/downloadclient/{id}\n   * @secure\n   */\n  v3ConfigDownloadclientDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v3/config/downloadclient/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ExtraFile\n   * @name V3ExtrafileList\n   * @request GET:/api/v3/extrafile\n   * @secure\n   */\n  v3ExtrafileList = (\n    query?: {\n      /** @format int32 */\n      movieId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ExtraFileResource[], any>({\n      path: `/api/v3/extrafile`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V3FilesystemList\n   * @request GET:/api/v3/filesystem\n   * @secure\n   */\n  v3FilesystemList = (\n    query?: {\n      path?: string;\n      /** @default false */\n      includeFiles?: boolean;\n      /** @default false */\n      allowFoldersWithoutTrailingSlashes?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/filesystem`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V3FilesystemTypeList\n   * @request GET:/api/v3/filesystem/type\n   * @secure\n   */\n  v3FilesystemTypeList = (\n    query?: {\n      path?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/filesystem/type`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V3FilesystemMediafilesList\n   * @request GET:/api/v3/filesystem/mediafiles\n   * @secure\n   */\n  v3FilesystemMediafilesList = (\n    query?: {\n      path?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/filesystem/mediafiles`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Health\n   * @name V3HealthList\n   * @request GET:/api/v3/health\n   * @secure\n   */\n  v3HealthList = (params: RequestParams = {}) =>\n    this.http.request<HealthResource[], any>({\n      path: `/api/v3/health`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V3HistoryList\n   * @request GET:/api/v3/history\n   * @secure\n   */\n  v3HistoryList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      includeMovie?: boolean;\n      eventType?: number[];\n      downloadId?: string;\n      movieIds?: number[];\n      languages?: number[];\n      quality?: number[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResourcePagingResource, any>({\n      path: `/api/v3/history`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V3HistorySinceList\n   * @request GET:/api/v3/history/since\n   * @secure\n   */\n  v3HistorySinceList = (\n    query?: {\n      /** @format date-time */\n      date?: string;\n      eventType?: MovieHistoryEventType;\n      /** @default false */\n      includeMovie?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResource[], any>({\n      path: `/api/v3/history/since`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V3HistoryMovieList\n   * @request GET:/api/v3/history/movie\n   * @secure\n   */\n  v3HistoryMovieList = (\n    query?: {\n      /** @format int32 */\n      movieId?: number;\n      eventType?: MovieHistoryEventType;\n      /** @default false */\n      includeMovie?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResource[], any>({\n      path: `/api/v3/history/movie`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V3HistoryFailedCreate\n   * @request POST:/api/v3/history/failed/{id}\n   * @secure\n   */\n  v3HistoryFailedCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/history/failed/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V3ConfigHostList\n   * @request GET:/api/v3/config/host\n   * @secure\n   */\n  v3ConfigHostList = (params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v3/config/host`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V3ConfigHostUpdate\n   * @request PUT:/api/v3/config/host/{id}\n   * @secure\n   */\n  v3ConfigHostUpdate = (id: string, data: HostConfigResource, params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v3/config/host/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V3ConfigHostDetail\n   * @request GET:/api/v3/config/host/{id}\n   * @secure\n   */\n  v3ConfigHostDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v3/config/host/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistList\n   * @request GET:/api/v3/importlist\n   * @secure\n   */\n  v3ImportlistList = (params: RequestParams = {}) =>\n    this.http.request<ImportListResource[], any>({\n      path: `/api/v3/importlist`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistCreate\n   * @request POST:/api/v3/importlist\n   * @secure\n   */\n  v3ImportlistCreate = (\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v3/importlist`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistUpdate\n   * @request PUT:/api/v3/importlist/{id}\n   * @secure\n   */\n  v3ImportlistUpdate = (\n    id: number,\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v3/importlist/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistDelete\n   * @request DELETE:/api/v3/importlist/{id}\n   * @secure\n   */\n  v3ImportlistDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistDetail\n   * @request GET:/api/v3/importlist/{id}\n   * @secure\n   */\n  v3ImportlistDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v3/importlist/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistBulkUpdate\n   * @request PUT:/api/v3/importlist/bulk\n   * @secure\n   */\n  v3ImportlistBulkUpdate = (data: ImportListBulkResource, params: RequestParams = {}) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v3/importlist/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistBulkDelete\n   * @request DELETE:/api/v3/importlist/bulk\n   * @secure\n   */\n  v3ImportlistBulkDelete = (data: ImportListBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistSchemaList\n   * @request GET:/api/v3/importlist/schema\n   * @secure\n   */\n  v3ImportlistSchemaList = (params: RequestParams = {}) =>\n    this.http.request<ImportListResource[], any>({\n      path: `/api/v3/importlist/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistTestCreate\n   * @request POST:/api/v3/importlist/test\n   * @secure\n   */\n  v3ImportlistTestCreate = (\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistTestallCreate\n   * @request POST:/api/v3/importlist/testall\n   * @secure\n   */\n  v3ImportlistTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistActionCreate\n   * @request POST:/api/v3/importlist/action/{name}\n   * @secure\n   */\n  v3ImportlistActionCreate = (name: string, data: ImportListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListConfig\n   * @name V3ConfigImportlistList\n   * @request GET:/api/v3/config/importlist\n   * @secure\n   */\n  v3ConfigImportlistList = (params: RequestParams = {}) =>\n    this.http.request<ImportListConfigResource, any>({\n      path: `/api/v3/config/importlist`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListConfig\n   * @name V3ConfigImportlistUpdate\n   * @request PUT:/api/v3/config/importlist/{id}\n   * @secure\n   */\n  v3ConfigImportlistUpdate = (id: string, data: ImportListConfigResource, params: RequestParams = {}) =>\n    this.http.request<ImportListConfigResource, any>({\n      path: `/api/v3/config/importlist/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListConfig\n   * @name V3ConfigImportlistDetail\n   * @request GET:/api/v3/config/importlist/{id}\n   * @secure\n   */\n  v3ConfigImportlistDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListConfigResource, any>({\n      path: `/api/v3/config/importlist/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ExclusionsList\n   * @request GET:/api/v3/exclusions\n   * @deprecated\n   * @secure\n   */\n  v3ExclusionsList = (params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource[], any>({\n      path: `/api/v3/exclusions`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ExclusionsCreate\n   * @request POST:/api/v3/exclusions\n   * @secure\n   */\n  v3ExclusionsCreate = (data: ImportListExclusionResource, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v3/exclusions`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ExclusionsPagedList\n   * @request GET:/api/v3/exclusions/paged\n   * @secure\n   */\n  v3ExclusionsPagedList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ImportListExclusionResourcePagingResource, any>({\n      path: `/api/v3/exclusions/paged`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ExclusionsUpdate\n   * @request PUT:/api/v3/exclusions/{id}\n   * @secure\n   */\n  v3ExclusionsUpdate = (id: string, data: ImportListExclusionResource, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v3/exclusions/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ExclusionsDelete\n   * @request DELETE:/api/v3/exclusions/{id}\n   * @secure\n   */\n  v3ExclusionsDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/exclusions/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ExclusionsDetail\n   * @request GET:/api/v3/exclusions/{id}\n   * @secure\n   */\n  v3ExclusionsDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v3/exclusions/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ExclusionsBulkCreate\n   * @request POST:/api/v3/exclusions/bulk\n   * @secure\n   */\n  v3ExclusionsBulkCreate = (data: ImportListExclusionResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/exclusions/bulk`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ExclusionsBulkDelete\n   * @request DELETE:/api/v3/exclusions/bulk\n   * @secure\n   */\n  v3ExclusionsBulkDelete = (data: ImportListExclusionBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/exclusions/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListMovies\n   * @name V3ImportlistMovieList\n   * @request GET:/api/v3/importlist/movie\n   * @secure\n   */\n  v3ImportlistMovieList = (\n    query?: {\n      /** @default false */\n      includeRecommendations?: boolean;\n      /** @default false */\n      includeTrending?: boolean;\n      /** @default false */\n      includePopular?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/movie`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListMovies\n   * @name V3ImportlistMovieCreate\n   * @request POST:/api/v3/importlist/movie\n   * @secure\n   */\n  v3ImportlistMovieCreate = (data: MovieResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/movie`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerList\n   * @request GET:/api/v3/indexer\n   * @secure\n   */\n  v3IndexerList = (params: RequestParams = {}) =>\n    this.http.request<IndexerResource[], any>({\n      path: `/api/v3/indexer`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerCreate\n   * @request POST:/api/v3/indexer\n   * @secure\n   */\n  v3IndexerCreate = (\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v3/indexer`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerUpdate\n   * @request PUT:/api/v3/indexer/{id}\n   * @secure\n   */\n  v3IndexerUpdate = (\n    id: number,\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v3/indexer/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerDelete\n   * @request DELETE:/api/v3/indexer/{id}\n   * @secure\n   */\n  v3IndexerDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerDetail\n   * @request GET:/api/v3/indexer/{id}\n   * @secure\n   */\n  v3IndexerDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v3/indexer/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerBulkUpdate\n   * @request PUT:/api/v3/indexer/bulk\n   * @secure\n   */\n  v3IndexerBulkUpdate = (data: IndexerBulkResource, params: RequestParams = {}) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v3/indexer/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerBulkDelete\n   * @request DELETE:/api/v3/indexer/bulk\n   * @secure\n   */\n  v3IndexerBulkDelete = (data: IndexerBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerSchemaList\n   * @request GET:/api/v3/indexer/schema\n   * @secure\n   */\n  v3IndexerSchemaList = (params: RequestParams = {}) =>\n    this.http.request<IndexerResource[], any>({\n      path: `/api/v3/indexer/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerTestCreate\n   * @request POST:/api/v3/indexer/test\n   * @secure\n   */\n  v3IndexerTestCreate = (\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerTestallCreate\n   * @request POST:/api/v3/indexer/testall\n   * @secure\n   */\n  v3IndexerTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerActionCreate\n   * @request POST:/api/v3/indexer/action/{name}\n   * @secure\n   */\n  v3IndexerActionCreate = (name: string, data: IndexerResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V3ConfigIndexerList\n   * @request GET:/api/v3/config/indexer\n   * @secure\n   */\n  v3ConfigIndexerList = (params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v3/config/indexer`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V3ConfigIndexerUpdate\n   * @request PUT:/api/v3/config/indexer/{id}\n   * @secure\n   */\n  v3ConfigIndexerUpdate = (id: string, data: IndexerConfigResource, params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v3/config/indexer/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V3ConfigIndexerDetail\n   * @request GET:/api/v3/config/indexer/{id}\n   * @secure\n   */\n  v3ConfigIndexerDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v3/config/indexer/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerFlag\n   * @name V3IndexerflagList\n   * @request GET:/api/v3/indexerflag\n   * @secure\n   */\n  v3IndexerflagList = (params: RequestParams = {}) =>\n    this.http.request<IndexerFlagResource[], any>({\n      path: `/api/v3/indexerflag`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Language\n   * @name V3LanguageList\n   * @request GET:/api/v3/language\n   * @secure\n   */\n  v3LanguageList = (params: RequestParams = {}) =>\n    this.http.request<LanguageResource[], any>({\n      path: `/api/v3/language`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Language\n   * @name V3LanguageDetail\n   * @request GET:/api/v3/language/{id}\n   * @secure\n   */\n  v3LanguageDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<LanguageResource, any>({\n      path: `/api/v3/language/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Localization\n   * @name V3LocalizationList\n   * @request GET:/api/v3/localization\n   * @secure\n   */\n  v3LocalizationList = (params: RequestParams = {}) =>\n    this.http.request<string, any>({\n      path: `/api/v3/localization`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Localization\n   * @name V3LocalizationLanguageList\n   * @request GET:/api/v3/localization/language\n   * @secure\n   */\n  v3LocalizationLanguageList = (params: RequestParams = {}) =>\n    this.http.request<LocalizationLanguageResource, any>({\n      path: `/api/v3/localization/language`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Log\n   * @name V3LogList\n   * @request GET:/api/v3/log\n   * @secure\n   */\n  v3LogList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      level?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<LogResourcePagingResource, any>({\n      path: `/api/v3/log`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LogFile\n   * @name V3LogFileList\n   * @request GET:/api/v3/log/file\n   * @secure\n   */\n  v3LogFileList = (params: RequestParams = {}) =>\n    this.http.request<LogFileResource[], any>({\n      path: `/api/v3/log/file`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LogFile\n   * @name V3LogFileDetail\n   * @request GET:/api/v3/log/file/{filename}\n   * @secure\n   */\n  v3LogFileDetail = (filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/log/file/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ManualImport\n   * @name V3ManualimportList\n   * @request GET:/api/v3/manualimport\n   * @secure\n   */\n  v3ManualimportList = (\n    query?: {\n      folder?: string;\n      downloadId?: string;\n      /** @format int32 */\n      movieId?: number;\n      /** @default true */\n      filterExistingFiles?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ManualImportResource[], any>({\n      path: `/api/v3/manualimport`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ManualImport\n   * @name V3ManualimportCreate\n   * @request POST:/api/v3/manualimport\n   * @secure\n   */\n  v3ManualimportCreate = (data: ManualImportReprocessResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/manualimport`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaCover\n   * @name V3MediacoverDetail\n   * @request GET:/api/v3/mediacover/{movieId}/{filename}\n   * @secure\n   */\n  v3MediacoverDetail = (movieId: number, filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/mediacover/${movieId}/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V3ConfigMediamanagementList\n   * @request GET:/api/v3/config/mediamanagement\n   * @secure\n   */\n  v3ConfigMediamanagementList = (params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v3/config/mediamanagement`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V3ConfigMediamanagementUpdate\n   * @request PUT:/api/v3/config/mediamanagement/{id}\n   * @secure\n   */\n  v3ConfigMediamanagementUpdate = (id: string, data: MediaManagementConfigResource, params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v3/config/mediamanagement/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V3ConfigMediamanagementDetail\n   * @request GET:/api/v3/config/mediamanagement/{id}\n   * @secure\n   */\n  v3ConfigMediamanagementDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v3/config/mediamanagement/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataList\n   * @request GET:/api/v3/metadata\n   * @secure\n   */\n  v3MetadataList = (params: RequestParams = {}) =>\n    this.http.request<MetadataResource[], any>({\n      path: `/api/v3/metadata`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataCreate\n   * @request POST:/api/v3/metadata\n   * @secure\n   */\n  v3MetadataCreate = (\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v3/metadata`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataUpdate\n   * @request PUT:/api/v3/metadata/{id}\n   * @secure\n   */\n  v3MetadataUpdate = (\n    id: number,\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v3/metadata/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataDelete\n   * @request DELETE:/api/v3/metadata/{id}\n   * @secure\n   */\n  v3MetadataDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/metadata/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataDetail\n   * @request GET:/api/v3/metadata/{id}\n   * @secure\n   */\n  v3MetadataDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v3/metadata/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataSchemaList\n   * @request GET:/api/v3/metadata/schema\n   * @secure\n   */\n  v3MetadataSchemaList = (params: RequestParams = {}) =>\n    this.http.request<MetadataResource[], any>({\n      path: `/api/v3/metadata/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataTestCreate\n   * @request POST:/api/v3/metadata/test\n   * @secure\n   */\n  v3MetadataTestCreate = (\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/metadata/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataTestallCreate\n   * @request POST:/api/v3/metadata/testall\n   * @secure\n   */\n  v3MetadataTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/metadata/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataActionCreate\n   * @request POST:/api/v3/metadata/action/{name}\n   * @secure\n   */\n  v3MetadataActionCreate = (name: string, data: MetadataResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/metadata/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataConfig\n   * @name V3ConfigMetadataList\n   * @request GET:/api/v3/config/metadata\n   * @secure\n   */\n  v3ConfigMetadataList = (params: RequestParams = {}) =>\n    this.http.request<MetadataConfigResource, any>({\n      path: `/api/v3/config/metadata`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataConfig\n   * @name V3ConfigMetadataUpdate\n   * @request PUT:/api/v3/config/metadata/{id}\n   * @secure\n   */\n  v3ConfigMetadataUpdate = (id: string, data: MetadataConfigResource, params: RequestParams = {}) =>\n    this.http.request<MetadataConfigResource, any>({\n      path: `/api/v3/config/metadata/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataConfig\n   * @name V3ConfigMetadataDetail\n   * @request GET:/api/v3/config/metadata/{id}\n   * @secure\n   */\n  v3ConfigMetadataDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MetadataConfigResource, any>({\n      path: `/api/v3/config/metadata/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Missing\n   * @name V3WantedMissingList\n   * @request GET:/api/v3/wanted/missing\n   * @secure\n   */\n  v3WantedMissingList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default true */\n      monitored?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MovieResourcePagingResource, any>({\n      path: `/api/v3/wanted/missing`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Movie\n   * @name V3MovieList\n   * @request GET:/api/v3/movie\n   * @secure\n   */\n  v3MovieList = (\n    query?: {\n      /** @format int32 */\n      tmdbId?: number;\n      /** @default false */\n      excludeLocalCovers?: boolean;\n      /** @format int32 */\n      languageId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MovieResource[], any>({\n      path: `/api/v3/movie`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Movie\n   * @name V3MovieCreate\n   * @request POST:/api/v3/movie\n   * @secure\n   */\n  v3MovieCreate = (data: MovieResource, params: RequestParams = {}) =>\n    this.http.request<MovieResource, any>({\n      path: `/api/v3/movie`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Movie\n   * @name V3MovieUpdate\n   * @request PUT:/api/v3/movie/{id}\n   * @secure\n   */\n  v3MovieUpdate = (\n    id: string,\n    data: MovieResource,\n    query?: {\n      /** @default false */\n      moveFiles?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MovieResource, any>({\n      path: `/api/v3/movie/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Movie\n   * @name V3MovieDelete\n   * @request DELETE:/api/v3/movie/{id}\n   * @secure\n   */\n  v3MovieDelete = (\n    id: number,\n    query?: {\n      /** @default false */\n      deleteFiles?: boolean;\n      /** @default false */\n      addImportExclusion?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/movie/${id}`,\n      method: \"DELETE\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Movie\n   * @name V3MovieDetail\n   * @request GET:/api/v3/movie/{id}\n   * @secure\n   */\n  v3MovieDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MovieResource, any>({\n      path: `/api/v3/movie/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieEditor\n   * @name V3MovieEditorUpdate\n   * @request PUT:/api/v3/movie/editor\n   * @secure\n   */\n  v3MovieEditorUpdate = (data: MovieEditorResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/movie/editor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieEditor\n   * @name V3MovieEditorDelete\n   * @request DELETE:/api/v3/movie/editor\n   * @secure\n   */\n  v3MovieEditorDelete = (data: MovieEditorResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/movie/editor`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieFile\n   * @name V3MoviefileList\n   * @request GET:/api/v3/moviefile\n   * @secure\n   */\n  v3MoviefileList = (\n    query?: {\n      movieId?: number[];\n      movieFileIds?: number[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MovieFileResource[], any>({\n      path: `/api/v3/moviefile`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieFile\n   * @name V3MoviefileUpdate\n   * @request PUT:/api/v3/moviefile/{id}\n   * @secure\n   */\n  v3MoviefileUpdate = (id: string, data: MovieFileResource, params: RequestParams = {}) =>\n    this.http.request<MovieFileResource, any>({\n      path: `/api/v3/moviefile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieFile\n   * @name V3MoviefileDelete\n   * @request DELETE:/api/v3/moviefile/{id}\n   * @secure\n   */\n  v3MoviefileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/moviefile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieFile\n   * @name V3MoviefileDetail\n   * @request GET:/api/v3/moviefile/{id}\n   * @secure\n   */\n  v3MoviefileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MovieFileResource, any>({\n      path: `/api/v3/moviefile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieFile\n   * @name V3MoviefileEditorUpdate\n   * @request PUT:/api/v3/moviefile/editor\n   * @deprecated\n   * @secure\n   */\n  v3MoviefileEditorUpdate = (data: MovieFileListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/moviefile/editor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieFile\n   * @name V3MoviefileBulkDelete\n   * @request DELETE:/api/v3/moviefile/bulk\n   * @secure\n   */\n  v3MoviefileBulkDelete = (data: MovieFileListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/moviefile/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieFile\n   * @name V3MoviefileBulkUpdate\n   * @request PUT:/api/v3/moviefile/bulk\n   * @secure\n   */\n  v3MoviefileBulkUpdate = (data: MovieFileResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/moviefile/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieFolder\n   * @name V3MovieFolderList\n   * @request GET:/api/v3/movie/{id}/folder\n   * @secure\n   */\n  v3MovieFolderList = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/movie/${id}/folder`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieImport\n   * @name V3MovieImportCreate\n   * @request POST:/api/v3/movie/import\n   * @secure\n   */\n  v3MovieImportCreate = (data: MovieResource[], params: RequestParams = {}) =>\n    this.http.request<MovieResource[], any>({\n      path: `/api/v3/movie/import`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieLookup\n   * @name V3MovieLookupTmdbList\n   * @request GET:/api/v3/movie/lookup/tmdb\n   * @secure\n   */\n  v3MovieLookupTmdbList = (\n    query?: {\n      /** @format int32 */\n      tmdbId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MovieResource, any>({\n      path: `/api/v3/movie/lookup/tmdb`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieLookup\n   * @name V3MovieLookupImdbList\n   * @request GET:/api/v3/movie/lookup/imdb\n   * @secure\n   */\n  v3MovieLookupImdbList = (\n    query?: {\n      imdbId?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MovieResource, any>({\n      path: `/api/v3/movie/lookup/imdb`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MovieLookup\n   * @name V3MovieLookupList\n   * @request GET:/api/v3/movie/lookup\n   * @secure\n   */\n  v3MovieLookupList = (\n    query?: {\n      term?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MovieResource[], any>({\n      path: `/api/v3/movie/lookup`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V3ConfigNamingList\n   * @request GET:/api/v3/config/naming\n   * @secure\n   */\n  v3ConfigNamingList = (params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v3/config/naming`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V3ConfigNamingUpdate\n   * @request PUT:/api/v3/config/naming/{id}\n   * @secure\n   */\n  v3ConfigNamingUpdate = (id: string, data: NamingConfigResource, params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v3/config/naming/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V3ConfigNamingDetail\n   * @request GET:/api/v3/config/naming/{id}\n   * @secure\n   */\n  v3ConfigNamingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v3/config/naming/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V3ConfigNamingExamplesList\n   * @request GET:/api/v3/config/naming/examples\n   * @secure\n   */\n  v3ConfigNamingExamplesList = (\n    query?: {\n      renameMovies?: boolean;\n      replaceIllegalCharacters?: boolean;\n      colonReplacementFormat?: ColonReplacementFormat;\n      standardMovieFormat?: string;\n      movieFolderFormat?: string;\n      /** @format int32 */\n      id?: number;\n      resourceName?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/config/naming/examples`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationList\n   * @request GET:/api/v3/notification\n   * @secure\n   */\n  v3NotificationList = (params: RequestParams = {}) =>\n    this.http.request<NotificationResource[], any>({\n      path: `/api/v3/notification`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationCreate\n   * @request POST:/api/v3/notification\n   * @secure\n   */\n  v3NotificationCreate = (\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v3/notification`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationUpdate\n   * @request PUT:/api/v3/notification/{id}\n   * @secure\n   */\n  v3NotificationUpdate = (\n    id: number,\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v3/notification/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationDelete\n   * @request DELETE:/api/v3/notification/{id}\n   * @secure\n   */\n  v3NotificationDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/notification/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationDetail\n   * @request GET:/api/v3/notification/{id}\n   * @secure\n   */\n  v3NotificationDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v3/notification/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationSchemaList\n   * @request GET:/api/v3/notification/schema\n   * @secure\n   */\n  v3NotificationSchemaList = (params: RequestParams = {}) =>\n    this.http.request<NotificationResource[], any>({\n      path: `/api/v3/notification/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationTestCreate\n   * @request POST:/api/v3/notification/test\n   * @secure\n   */\n  v3NotificationTestCreate = (\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/notification/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationTestallCreate\n   * @request POST:/api/v3/notification/testall\n   * @secure\n   */\n  v3NotificationTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/notification/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationActionCreate\n   * @request POST:/api/v3/notification/action/{name}\n   * @secure\n   */\n  v3NotificationActionCreate = (name: string, data: NotificationResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/notification/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Parse\n   * @name V3ParseList\n   * @request GET:/api/v3/parse\n   * @secure\n   */\n  v3ParseList = (\n    query?: {\n      title?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ParseResource, any>({\n      path: `/api/v3/parse`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionUpdate\n   * @request PUT:/api/v3/qualitydefinition/{id}\n   * @secure\n   */\n  v3QualitydefinitionUpdate = (id: string, data: QualityDefinitionResource, params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource, any>({\n      path: `/api/v3/qualitydefinition/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionDetail\n   * @request GET:/api/v3/qualitydefinition/{id}\n   * @secure\n   */\n  v3QualitydefinitionDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource, any>({\n      path: `/api/v3/qualitydefinition/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionList\n   * @request GET:/api/v3/qualitydefinition\n   * @secure\n   */\n  v3QualitydefinitionList = (params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource[], any>({\n      path: `/api/v3/qualitydefinition`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionUpdateUpdate\n   * @request PUT:/api/v3/qualitydefinition/update\n   * @secure\n   */\n  v3QualitydefinitionUpdateUpdate = (data: QualityDefinitionResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/qualitydefinition/update`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionLimitsList\n   * @request GET:/api/v3/qualitydefinition/limits\n   * @secure\n   */\n  v3QualitydefinitionLimitsList = (params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionLimitsResource, any>({\n      path: `/api/v3/qualitydefinition/limits`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileCreate\n   * @request POST:/api/v3/qualityprofile\n   * @secure\n   */\n  v3QualityprofileCreate = (data: QualityProfileResource, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v3/qualityprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileList\n   * @request GET:/api/v3/qualityprofile\n   * @secure\n   */\n  v3QualityprofileList = (params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource[], any>({\n      path: `/api/v3/qualityprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileDelete\n   * @request DELETE:/api/v3/qualityprofile/{id}\n   * @secure\n   */\n  v3QualityprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/qualityprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileUpdate\n   * @request PUT:/api/v3/qualityprofile/{id}\n   * @secure\n   */\n  v3QualityprofileUpdate = (id: string, data: QualityProfileResource, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v3/qualityprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileDetail\n   * @request GET:/api/v3/qualityprofile/{id}\n   * @secure\n   */\n  v3QualityprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v3/qualityprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfileSchema\n   * @name V3QualityprofileSchemaList\n   * @request GET:/api/v3/qualityprofile/schema\n   * @secure\n   */\n  v3QualityprofileSchemaList = (params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v3/qualityprofile/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V3QueueDelete\n   * @request DELETE:/api/v3/queue/{id}\n   * @secure\n   */\n  v3QueueDelete = (\n    id: number,\n    query?: {\n      /** @default true */\n      removeFromClient?: boolean;\n      /** @default false */\n      blocklist?: boolean;\n      /** @default false */\n      skipRedownload?: boolean;\n      /** @default false */\n      changeCategory?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/queue/${id}`,\n      method: \"DELETE\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V3QueueBulkDelete\n   * @request DELETE:/api/v3/queue/bulk\n   * @secure\n   */\n  v3QueueBulkDelete = (\n    data: QueueBulkResource,\n    query?: {\n      /** @default true */\n      removeFromClient?: boolean;\n      /** @default false */\n      blocklist?: boolean;\n      /** @default false */\n      skipRedownload?: boolean;\n      /** @default false */\n      changeCategory?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/queue/bulk`,\n      method: \"DELETE\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V3QueueList\n   * @request GET:/api/v3/queue\n   * @secure\n   */\n  v3QueueList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeUnknownMovieItems?: boolean;\n      /** @default false */\n      includeMovie?: boolean;\n      movieIds?: number[];\n      protocol?: DownloadProtocol;\n      languages?: number[];\n      quality?: number[];\n      status?: QueueStatus[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<QueueResourcePagingResource, any>({\n      path: `/api/v3/queue`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueAction\n   * @name V3QueueGrabCreate\n   * @request POST:/api/v3/queue/grab/{id}\n   * @secure\n   */\n  v3QueueGrabCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/queue/grab/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueAction\n   * @name V3QueueGrabBulkCreate\n   * @request POST:/api/v3/queue/grab/bulk\n   * @secure\n   */\n  v3QueueGrabBulkCreate = (data: QueueBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/queue/grab/bulk`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueDetails\n   * @name V3QueueDetailsList\n   * @request GET:/api/v3/queue/details\n   * @secure\n   */\n  v3QueueDetailsList = (\n    query?: {\n      /** @format int32 */\n      movieId?: number;\n      /** @default false */\n      includeMovie?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<QueueResource[], any>({\n      path: `/api/v3/queue/details`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueStatus\n   * @name V3QueueStatusList\n   * @request GET:/api/v3/queue/status\n   * @secure\n   */\n  v3QueueStatusList = (params: RequestParams = {}) =>\n    this.http.request<QueueStatusResource, any>({\n      path: `/api/v3/queue/status`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Release\n   * @name V3ReleaseCreate\n   * @request POST:/api/v3/release\n   * @secure\n   */\n  v3ReleaseCreate = (data: ReleaseResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/release`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Release\n   * @name V3ReleaseList\n   * @request GET:/api/v3/release\n   * @secure\n   */\n  v3ReleaseList = (\n    query?: {\n      /** @format int32 */\n      movieId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ReleaseResource[], any>({\n      path: `/api/v3/release`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileCreate\n   * @request POST:/api/v3/releaseprofile\n   * @secure\n   */\n  v3ReleaseprofileCreate = (data: ReleaseProfileResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v3/releaseprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileList\n   * @request GET:/api/v3/releaseprofile\n   * @secure\n   */\n  v3ReleaseprofileList = (params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource[], any>({\n      path: `/api/v3/releaseprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileDelete\n   * @request DELETE:/api/v3/releaseprofile/{id}\n   * @secure\n   */\n  v3ReleaseprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/releaseprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileUpdate\n   * @request PUT:/api/v3/releaseprofile/{id}\n   * @secure\n   */\n  v3ReleaseprofileUpdate = (id: string, data: ReleaseProfileResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v3/releaseprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileDetail\n   * @request GET:/api/v3/releaseprofile/{id}\n   * @secure\n   */\n  v3ReleaseprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v3/releaseprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleasePush\n   * @name V3ReleasePushCreate\n   * @request POST:/api/v3/release/push\n   * @secure\n   */\n  v3ReleasePushCreate = (data: ReleaseResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseResource[], any>({\n      path: `/api/v3/release/push`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingCreate\n   * @request POST:/api/v3/remotepathmapping\n   * @secure\n   */\n  v3RemotepathmappingCreate = (data: RemotePathMappingResource, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v3/remotepathmapping`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingList\n   * @request GET:/api/v3/remotepathmapping\n   * @secure\n   */\n  v3RemotepathmappingList = (params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource[], any>({\n      path: `/api/v3/remotepathmapping`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingDelete\n   * @request DELETE:/api/v3/remotepathmapping/{id}\n   * @secure\n   */\n  v3RemotepathmappingDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/remotepathmapping/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingUpdate\n   * @request PUT:/api/v3/remotepathmapping/{id}\n   * @secure\n   */\n  v3RemotepathmappingUpdate = (id: string, data: RemotePathMappingResource, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v3/remotepathmapping/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingDetail\n   * @request GET:/api/v3/remotepathmapping/{id}\n   * @secure\n   */\n  v3RemotepathmappingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v3/remotepathmapping/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RenameMovie\n   * @name V3RenameList\n   * @request GET:/api/v3/rename\n   * @secure\n   */\n  v3RenameList = (\n    query?: {\n      movieId?: number[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<RenameMovieResource[], any>({\n      path: `/api/v3/rename`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V3RootfolderCreate\n   * @request POST:/api/v3/rootfolder\n   * @secure\n   */\n  v3RootfolderCreate = (data: RootFolderResource, params: RequestParams = {}) =>\n    this.http.request<RootFolderResource, any>({\n      path: `/api/v3/rootfolder`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V3RootfolderList\n   * @request GET:/api/v3/rootfolder\n   * @secure\n   */\n  v3RootfolderList = (params: RequestParams = {}) =>\n    this.http.request<RootFolderResource[], any>({\n      path: `/api/v3/rootfolder`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V3RootfolderDelete\n   * @request DELETE:/api/v3/rootfolder/{id}\n   * @secure\n   */\n  v3RootfolderDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/rootfolder/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V3RootfolderDetail\n   * @request GET:/api/v3/rootfolder/{id}\n   * @secure\n   */\n  v3RootfolderDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<RootFolderResource, any>({\n      path: `/api/v3/rootfolder/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemStatusList\n   * @request GET:/api/v3/system/status\n   * @secure\n   */\n  v3SystemStatusList = (params: RequestParams = {}) =>\n    this.http.request<SystemResource, any>({\n      path: `/api/v3/system/status`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemRoutesList\n   * @request GET:/api/v3/system/routes\n   * @secure\n   */\n  v3SystemRoutesList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/routes`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemRoutesDuplicateList\n   * @request GET:/api/v3/system/routes/duplicate\n   * @secure\n   */\n  v3SystemRoutesDuplicateList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/routes/duplicate`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemShutdownCreate\n   * @request POST:/api/v3/system/shutdown\n   * @secure\n   */\n  v3SystemShutdownCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/shutdown`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemRestartCreate\n   * @request POST:/api/v3/system/restart\n   * @secure\n   */\n  v3SystemRestartCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/restart`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagList\n   * @request GET:/api/v3/tag\n   * @secure\n   */\n  v3TagList = (params: RequestParams = {}) =>\n    this.http.request<TagResource[], any>({\n      path: `/api/v3/tag`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagCreate\n   * @request POST:/api/v3/tag\n   * @secure\n   */\n  v3TagCreate = (data: TagResource, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v3/tag`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagUpdate\n   * @request PUT:/api/v3/tag/{id}\n   * @secure\n   */\n  v3TagUpdate = (id: string, data: TagResource, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v3/tag/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagDelete\n   * @request DELETE:/api/v3/tag/{id}\n   * @secure\n   */\n  v3TagDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/tag/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagDetail\n   * @request GET:/api/v3/tag/{id}\n   * @secure\n   */\n  v3TagDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v3/tag/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TagDetails\n   * @name V3TagDetailList\n   * @request GET:/api/v3/tag/detail\n   * @secure\n   */\n  v3TagDetailList = (params: RequestParams = {}) =>\n    this.http.request<TagDetailsResource[], any>({\n      path: `/api/v3/tag/detail`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TagDetails\n   * @name V3TagDetailDetail\n   * @request GET:/api/v3/tag/detail/{id}\n   * @secure\n   */\n  v3TagDetailDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TagDetailsResource, any>({\n      path: `/api/v3/tag/detail/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Task\n   * @name V3SystemTaskList\n   * @request GET:/api/v3/system/task\n   * @secure\n   */\n  v3SystemTaskList = (params: RequestParams = {}) =>\n    this.http.request<TaskResource[], any>({\n      path: `/api/v3/system/task`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Task\n   * @name V3SystemTaskDetail\n   * @request GET:/api/v3/system/task/{id}\n   * @secure\n   */\n  v3SystemTaskDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TaskResource, any>({\n      path: `/api/v3/system/task/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V3ConfigUiUpdate\n   * @request PUT:/api/v3/config/ui/{id}\n   * @secure\n   */\n  v3ConfigUiUpdate = (id: string, data: UiConfigResource, params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v3/config/ui/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V3ConfigUiDetail\n   * @request GET:/api/v3/config/ui/{id}\n   * @secure\n   */\n  v3ConfigUiDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v3/config/ui/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V3ConfigUiList\n   * @request GET:/api/v3/config/ui\n   * @secure\n   */\n  v3ConfigUiList = (params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v3/config/ui`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Update\n   * @name V3UpdateList\n   * @request GET:/api/v3/update\n   * @secure\n   */\n  v3UpdateList = (params: RequestParams = {}) =>\n    this.http.request<UpdateResource[], any>({\n      path: `/api/v3/update`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UpdateLogFile\n   * @name V3LogFileUpdateList\n   * @request GET:/api/v3/log/file/update\n   * @secure\n   */\n  v3LogFileUpdateList = (params: RequestParams = {}) =>\n    this.http.request<LogFileResource[], any>({\n      path: `/api/v3/log/file/update`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UpdateLogFile\n   * @name V3LogFileUpdateDetail\n   * @request GET:/api/v3/log/file/update/{filename}\n   * @secure\n   */\n  v3LogFileUpdateDetail = (filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/log/file/update/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/radarr/Content.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Content<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name ContentDetail\n   * @request GET:/content/{path}\n   * @secure\n   */\n  contentDetail = (path: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/content/${path}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/radarr/Feed.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\nimport { CalendarReleaseType } from \"./data-contracts\";\n\nexport class Feed<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags CalendarFeed\n   * @name V3CalendarRadarrIcsList\n   * @request GET:/feed/v3/calendar/radarr.ics\n   * @secure\n   */\n  v3CalendarRadarrIcsList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 7\n       */\n      pastDays?: number;\n      /**\n       * @format int32\n       * @default 28\n       */\n      futureDays?: number;\n      /** @default \"\" */\n      tags?: string;\n      /** @default false */\n      unmonitored?: boolean;\n      releaseTypes?: CalendarReleaseType[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/feed/v3/calendar/radarr.ics`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/radarr/Login.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { ContentType, HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Login<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Authentication\n   * @name LoginCreate\n   * @request POST:/login\n   * @secure\n   */\n  loginCreate = (\n    data: {\n      username?: string;\n      password?: string;\n      rememberMe?: string;\n    },\n    query?: {\n      returnUrl?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/login`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.FormData,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name LoginList\n   * @request GET:/login\n   * @secure\n   */\n  loginList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/login`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/radarr/Logout.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Logout<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Authentication\n   * @name LogoutList\n   * @request GET:/logout\n   * @secure\n   */\n  logoutList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/logout`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/radarr/Path.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Path<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name GetPath\n   * @request GET:/{path}\n   * @secure\n   */\n  getPath = (path: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/${path}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/radarr/Ping.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\nimport { PingResource } from \"./data-contracts\";\n\nexport class Ping<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Ping\n   * @name PingList\n   * @request GET:/ping\n   * @secure\n   */\n  pingList = (params: RequestParams = {}) =>\n    this.http.request<PingResource, any>({\n      path: `/ping`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Ping\n   * @name HeadPing\n   * @request HEAD:/ping\n   * @secure\n   */\n  headPing = (params: RequestParams = {}) =>\n    this.http.request<PingResource, any>({\n      path: `/ping`,\n      method: \"HEAD\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/radarr/data-contracts.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nexport enum UpdateMechanism {\n  BuiltIn = \"builtIn\",\n  Script = \"script\",\n  External = \"external\",\n  Apt = \"apt\",\n  Docker = \"docker\",\n}\n\nexport enum TrackedDownloadStatus {\n  Ok = \"ok\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum TrackedDownloadState {\n  Downloading = \"downloading\",\n  ImportBlocked = \"importBlocked\",\n  ImportPending = \"importPending\",\n  Importing = \"importing\",\n  Imported = \"imported\",\n  FailedPending = \"failedPending\",\n  Failed = \"failed\",\n  Ignored = \"ignored\",\n}\n\nexport enum TMDbCountryCode {\n  Au = \"au\",\n  Br = \"br\",\n  Ca = \"ca\",\n  Fr = \"fr\",\n  De = \"de\",\n  Gb = \"gb\",\n  In = \"in\",\n  Ie = \"ie\",\n  It = \"it\",\n  Nz = \"nz\",\n  Ro = \"ro\",\n  Es = \"es\",\n  Us = \"us\",\n}\n\nexport enum SourceType {\n  Tmdb = \"tmdb\",\n  Mappings = \"mappings\",\n  User = \"user\",\n  Indexer = \"indexer\",\n}\n\nexport enum SortDirection {\n  Default = \"default\",\n  Ascending = \"ascending\",\n  Descending = \"descending\",\n}\n\nexport enum RuntimeMode {\n  Console = \"console\",\n  Service = \"service\",\n  Tray = \"tray\",\n}\n\nexport enum RescanAfterRefreshType {\n  Always = \"always\",\n  AfterManual = \"afterManual\",\n  Never = \"never\",\n}\n\nexport enum RejectionType {\n  Permanent = \"permanent\",\n  Temporary = \"temporary\",\n}\n\nexport enum RatingType {\n  User = \"user\",\n  Critic = \"critic\",\n}\n\nexport enum QueueStatus {\n  Unknown = \"unknown\",\n  Queued = \"queued\",\n  Paused = \"paused\",\n  Downloading = \"downloading\",\n  Completed = \"completed\",\n  Failed = \"failed\",\n  Warning = \"warning\",\n  Delay = \"delay\",\n  DownloadClientUnavailable = \"downloadClientUnavailable\",\n  Fallback = \"fallback\",\n}\n\nexport enum QualitySource {\n  Unknown = \"unknown\",\n  Cam = \"cam\",\n  Telesync = \"telesync\",\n  Telecine = \"telecine\",\n  Workprint = \"workprint\",\n  Dvd = \"dvd\",\n  Tv = \"tv\",\n  Webdl = \"webdl\",\n  Webrip = \"webrip\",\n  Bluray = \"bluray\",\n}\n\nexport enum ProxyType {\n  Http = \"http\",\n  Socks4 = \"socks4\",\n  Socks5 = \"socks5\",\n}\n\nexport enum ProviderMessageType {\n  Info = \"info\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum ProperDownloadTypes {\n  PreferAndUpgrade = \"preferAndUpgrade\",\n  DoNotUpgrade = \"doNotUpgrade\",\n  DoNotPrefer = \"doNotPrefer\",\n}\n\nexport enum PrivacyLevel {\n  Normal = \"normal\",\n  Password = \"password\",\n  ApiKey = \"apiKey\",\n  UserName = \"userName\",\n}\n\nexport enum MovieStatusType {\n  Tba = \"tba\",\n  Announced = \"announced\",\n  InCinemas = \"inCinemas\",\n  Released = \"released\",\n  Deleted = \"deleted\",\n}\n\nexport enum MovieRuntimeFormatType {\n  HoursMinutes = \"hoursMinutes\",\n  Minutes = \"minutes\",\n}\n\nexport enum MovieHistoryEventType {\n  Unknown = \"unknown\",\n  Grabbed = \"grabbed\",\n  DownloadFolderImported = \"downloadFolderImported\",\n  DownloadFailed = \"downloadFailed\",\n  MovieFileDeleted = \"movieFileDeleted\",\n  MovieFolderImported = \"movieFolderImported\",\n  MovieFileRenamed = \"movieFileRenamed\",\n  DownloadIgnored = \"downloadIgnored\",\n}\n\nexport enum MonitorTypes {\n  MovieOnly = \"movieOnly\",\n  MovieAndCollection = \"movieAndCollection\",\n  None = \"none\",\n}\n\nexport enum Modifier {\n  None = \"none\",\n  Regional = \"regional\",\n  Screener = \"screener\",\n  Rawhd = \"rawhd\",\n  Brdisk = \"brdisk\",\n  Remux = \"remux\",\n}\n\nexport enum MediaCoverTypes {\n  Unknown = \"unknown\",\n  Poster = \"poster\",\n  Banner = \"banner\",\n  Fanart = \"fanart\",\n  Screenshot = \"screenshot\",\n  Headshot = \"headshot\",\n  Clearlogo = \"clearlogo\",\n}\n\nexport enum ImportListType {\n  Program = \"program\",\n  Tmdb = \"tmdb\",\n  Trakt = \"trakt\",\n  Plex = \"plex\",\n  Simkl = \"simkl\",\n  Other = \"other\",\n  Advanced = \"advanced\",\n}\n\nexport enum HealthCheckResult {\n  Ok = \"ok\",\n  Notice = \"notice\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum FileDateType {\n  None = \"none\",\n  Cinemas = \"cinemas\",\n  Release = \"release\",\n}\n\nexport enum ExtraFileType {\n  Subtitle = \"subtitle\",\n  Metadata = \"metadata\",\n  Other = \"other\",\n}\n\nexport enum DownloadProtocol {\n  Unknown = \"unknown\",\n  Usenet = \"usenet\",\n  Torrent = \"torrent\",\n}\n\nexport enum DatabaseType {\n  SqLite = \"sqLite\",\n  PostgreSQL = \"postgreSQL\",\n}\n\nexport enum CreditType {\n  Cast = \"cast\",\n  Crew = \"crew\",\n}\n\nexport enum CommandTrigger {\n  Unspecified = \"unspecified\",\n  Manual = \"manual\",\n  Scheduled = \"scheduled\",\n}\n\nexport enum CommandStatus {\n  Queued = \"queued\",\n  Started = \"started\",\n  Completed = \"completed\",\n  Failed = \"failed\",\n  Aborted = \"aborted\",\n  Cancelled = \"cancelled\",\n  Orphaned = \"orphaned\",\n}\n\nexport enum CommandResult {\n  Unknown = \"unknown\",\n  Successful = \"successful\",\n  Unsuccessful = \"unsuccessful\",\n}\n\nexport enum CommandPriority {\n  Normal = \"normal\",\n  High = \"high\",\n  Low = \"low\",\n}\n\nexport enum ColonReplacementFormat {\n  Delete = \"delete\",\n  Dash = \"dash\",\n  SpaceDash = \"spaceDash\",\n  SpaceDashSpace = \"spaceDashSpace\",\n  Smart = \"smart\",\n}\n\nexport enum CertificateValidationType {\n  Enabled = \"enabled\",\n  DisabledForLocalAddresses = \"disabledForLocalAddresses\",\n  Disabled = \"disabled\",\n}\n\nexport enum CalendarReleaseType {\n  CinemaRelease = \"cinemaRelease\",\n  DigitalRelease = \"digitalRelease\",\n  PhysicalRelease = \"physicalRelease\",\n}\n\nexport enum BackupType {\n  Scheduled = \"scheduled\",\n  Manual = \"manual\",\n  Update = \"update\",\n}\n\nexport enum AuthenticationType {\n  None = \"none\",\n  Basic = \"basic\",\n  Forms = \"forms\",\n  External = \"external\",\n}\n\nexport enum AuthenticationRequiredType {\n  Enabled = \"enabled\",\n  DisabledForLocalAddresses = \"disabledForLocalAddresses\",\n}\n\nexport enum ApplyTags {\n  Add = \"add\",\n  Remove = \"remove\",\n  Replace = \"replace\",\n}\n\nexport enum AddMovieMethod {\n  Manual = \"manual\",\n  List = \"list\",\n  Collection = \"collection\",\n}\n\nexport interface AddMovieOptions {\n  ignoreEpisodesWithFiles?: boolean;\n  ignoreEpisodesWithoutFiles?: boolean;\n  monitor?: MonitorTypes;\n  searchForMovie?: boolean;\n  addMethod?: AddMovieMethod;\n}\n\nexport interface AlternativeTitleResource {\n  /** @format int32 */\n  id?: number;\n  sourceType?: SourceType;\n  /** @format int32 */\n  movieMetadataId?: number;\n  title?: string | null;\n  cleanTitle?: string | null;\n}\n\nexport interface ApiInfoResource {\n  current?: string | null;\n  deprecated?: string[] | null;\n}\n\nexport interface AutoTaggingResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  removeTagsAutomatically?: boolean;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  specifications?: AutoTaggingSpecificationSchema[] | null;\n}\n\nexport interface AutoTaggingSpecificationSchema {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  implementation?: string | null;\n  implementationName?: string | null;\n  negate?: boolean;\n  required?: boolean;\n  fields?: Field[] | null;\n}\n\nexport interface BackupResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  path?: string | null;\n  type?: BackupType;\n  /** @format int64 */\n  size?: number;\n  /** @format date-time */\n  time?: string;\n}\n\nexport interface BlocklistBulkResource {\n  ids?: number[] | null;\n}\n\nexport interface BlocklistResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  movieId?: number;\n  sourceTitle?: string | null;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format date-time */\n  date?: string;\n  protocol?: DownloadProtocol;\n  indexer?: string | null;\n  message?: string | null;\n  movie?: MovieResource;\n}\n\nexport interface BlocklistResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: BlocklistResource[] | null;\n}\n\nexport interface CollectionMovieResource {\n  /** @format int32 */\n  tmdbId?: number;\n  imdbId?: string | null;\n  title?: string | null;\n  cleanTitle?: string | null;\n  sortTitle?: string | null;\n  status?: MovieStatusType;\n  overview?: string | null;\n  /** @format int32 */\n  runtime?: number;\n  images?: MediaCover[] | null;\n  /** @format int32 */\n  year?: number;\n  ratings?: Ratings;\n  genres?: string[] | null;\n  folder?: string | null;\n  isExisting?: boolean;\n  isExcluded?: boolean;\n}\n\nexport interface CollectionResource {\n  /** @format int32 */\n  id?: number;\n  title?: string | null;\n  sortTitle?: string | null;\n  /** @format int32 */\n  tmdbId?: number;\n  images?: MediaCover[] | null;\n  overview?: string | null;\n  monitored?: boolean;\n  rootFolderPath?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number;\n  searchOnAdd?: boolean;\n  minimumAvailability?: MovieStatusType;\n  movies?: CollectionMovieResource[] | null;\n  /** @format int32 */\n  missingMovies?: number;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n}\n\nexport interface CollectionUpdateResource {\n  collectionIds?: number[] | null;\n  monitored?: boolean | null;\n  monitorMovies?: boolean | null;\n  searchOnAdd?: boolean | null;\n  /** @format int32 */\n  qualityProfileId?: number | null;\n  rootFolderPath?: string | null;\n  minimumAvailability?: MovieStatusType;\n}\n\nexport interface Command {\n  sendUpdatesToClient?: boolean;\n  updateScheduledTask?: boolean;\n  completionMessage?: string | null;\n  requiresDiskAccess?: boolean;\n  isExclusive?: boolean;\n  isTypeExclusive?: boolean;\n  isLongRunning?: boolean;\n  name?: string | null;\n  /** @format date-time */\n  lastExecutionTime?: string | null;\n  /** @format date-time */\n  lastStartTime?: string | null;\n  trigger?: CommandTrigger;\n  suppressMessages?: boolean;\n  clientUserAgent?: string | null;\n}\n\nexport interface CommandResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  commandName?: string | null;\n  message?: string | null;\n  body?: Command;\n  priority?: CommandPriority;\n  status?: CommandStatus;\n  result?: CommandResult;\n  /** @format date-time */\n  queued?: string;\n  /** @format date-time */\n  started?: string | null;\n  /** @format date-time */\n  ended?: string | null;\n  /** @format date-span */\n  duration?: string | null;\n  exception?: string | null;\n  trigger?: CommandTrigger;\n  clientUserAgent?: string | null;\n  /** @format date-time */\n  stateChangeTime?: string | null;\n  sendUpdatesToClient?: boolean;\n  updateScheduledTask?: boolean;\n  /** @format date-time */\n  lastExecutionTime?: string | null;\n}\n\nexport interface CreditResource {\n  /** @format int32 */\n  id?: number;\n  personName?: string | null;\n  creditTmdbId?: string | null;\n  /** @format int32 */\n  personTmdbId?: number;\n  /** @format int32 */\n  movieMetadataId?: number;\n  images?: MediaCover[] | null;\n  department?: string | null;\n  job?: string | null;\n  character?: string | null;\n  /** @format int32 */\n  order?: number;\n  type?: CreditType;\n}\n\nexport interface CustomFilterResource {\n  /** @format int32 */\n  id?: number;\n  type?: string | null;\n  label?: string | null;\n  filters?: Record<string, any>[] | null;\n}\n\nexport interface CustomFormatBulkResource {\n  /** @uniqueItems true */\n  ids?: number[] | null;\n  includeCustomFormatWhenRenaming?: boolean | null;\n}\n\nexport interface CustomFormatResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  includeCustomFormatWhenRenaming?: boolean | null;\n  specifications?: CustomFormatSpecificationSchema[] | null;\n}\n\nexport interface CustomFormatSpecificationSchema {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  implementation?: string | null;\n  implementationName?: string | null;\n  infoLink?: string | null;\n  negate?: boolean;\n  required?: boolean;\n  fields?: Field[] | null;\n  presets?: CustomFormatSpecificationSchema[] | null;\n}\n\nexport interface DelayProfileResource {\n  /** @format int32 */\n  id?: number;\n  enableUsenet?: boolean;\n  enableTorrent?: boolean;\n  preferredProtocol?: DownloadProtocol;\n  /** @format int32 */\n  usenetDelay?: number;\n  /** @format int32 */\n  torrentDelay?: number;\n  bypassIfHighestQuality?: boolean;\n  bypassIfAboveCustomFormatScore?: boolean;\n  /** @format int32 */\n  minimumCustomFormatScore?: number;\n  /** @format int32 */\n  order?: number;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n}\n\nexport interface DiskSpaceResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  label?: string | null;\n  /** @format int64 */\n  freeSpace?: number;\n  /** @format int64 */\n  totalSpace?: number;\n}\n\nexport interface DownloadClientBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enable?: boolean | null;\n  /** @format int32 */\n  priority?: number | null;\n  removeCompletedDownloads?: boolean | null;\n  removeFailedDownloads?: boolean | null;\n}\n\nexport interface DownloadClientConfigResource {\n  /** @format int32 */\n  id?: number;\n  downloadClientWorkingFolders?: string | null;\n  enableCompletedDownloadHandling?: boolean;\n  /** @format int32 */\n  checkForFinishedDownloadInterval?: number;\n  autoRedownloadFailed?: boolean;\n  autoRedownloadFailedFromInteractiveSearch?: boolean;\n}\n\nexport interface DownloadClientResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: DownloadClientResource[] | null;\n  enable?: boolean;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  priority?: number;\n  removeCompletedDownloads?: boolean;\n  removeFailedDownloads?: boolean;\n}\n\nexport interface ExtraFileResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  movieId?: number;\n  /** @format int32 */\n  movieFileId?: number | null;\n  relativePath?: string | null;\n  extension?: string | null;\n  languageTags?: string[] | null;\n  title?: string | null;\n  type?: ExtraFileType;\n}\n\nexport interface Field {\n  /** @format int32 */\n  order?: number;\n  name?: string | null;\n  label?: string | null;\n  unit?: string | null;\n  helpText?: string | null;\n  helpTextWarning?: string | null;\n  helpLink?: string | null;\n  value?: any;\n  type?: string | null;\n  advanced?: boolean;\n  selectOptions?: SelectOption[] | null;\n  selectOptionsProviderAction?: string | null;\n  section?: string | null;\n  hidden?: string | null;\n  privacy?: PrivacyLevel;\n  placeholder?: string | null;\n  isFloat?: boolean;\n}\n\nexport interface HealthResource {\n  /** @format int32 */\n  id?: number;\n  source?: string | null;\n  type?: HealthCheckResult;\n  message?: string | null;\n  wikiUrl?: string | null;\n}\n\nexport interface HistoryResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  movieId?: number;\n  sourceTitle?: string | null;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  qualityCutoffNotMet?: boolean;\n  /** @format date-time */\n  date?: string;\n  downloadId?: string | null;\n  eventType?: MovieHistoryEventType;\n  data?: Record<string, string | null>;\n  movie?: MovieResource;\n}\n\nexport interface HistoryResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: HistoryResource[] | null;\n}\n\nexport interface HostConfigResource {\n  /** @format int32 */\n  id?: number;\n  bindAddress?: string | null;\n  /** @format int32 */\n  port?: number;\n  /** @format int32 */\n  sslPort?: number;\n  enableSsl?: boolean;\n  launchBrowser?: boolean;\n  authenticationMethod?: AuthenticationType;\n  authenticationRequired?: AuthenticationRequiredType;\n  analyticsEnabled?: boolean;\n  username?: string | null;\n  password?: string | null;\n  passwordConfirmation?: string | null;\n  logLevel?: string | null;\n  /** @format int32 */\n  logSizeLimit?: number;\n  consoleLogLevel?: string | null;\n  branch?: string | null;\n  apiKey?: string | null;\n  sslCertPath?: string | null;\n  sslCertPassword?: string | null;\n  urlBase?: string | null;\n  instanceName?: string | null;\n  applicationUrl?: string | null;\n  updateAutomatically?: boolean;\n  updateMechanism?: UpdateMechanism;\n  updateScriptPath?: string | null;\n  proxyEnabled?: boolean;\n  proxyType?: ProxyType;\n  proxyHostname?: string | null;\n  /** @format int32 */\n  proxyPort?: number;\n  proxyUsername?: string | null;\n  proxyPassword?: string | null;\n  proxyBypassFilter?: string | null;\n  proxyBypassLocalAddresses?: boolean;\n  certificateValidation?: CertificateValidationType;\n  backupFolder?: string | null;\n  /** @format int32 */\n  backupInterval?: number;\n  /** @format int32 */\n  backupRetention?: number;\n  trustCgnatIpAddresses?: boolean;\n}\n\nexport interface ImportListBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enabled?: boolean | null;\n  enableAuto?: boolean | null;\n  rootFolderPath?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number | null;\n  minimumAvailability?: MovieStatusType;\n}\n\nexport interface ImportListConfigResource {\n  /** @format int32 */\n  id?: number;\n  listSyncLevel?: string | null;\n}\n\nexport interface ImportListExclusionBulkResource {\n  /** @uniqueItems true */\n  ids?: number[] | null;\n}\n\nexport interface ImportListExclusionResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: ImportListExclusionResource[] | null;\n  /** @format int32 */\n  tmdbId?: number;\n  movieTitle?: string | null;\n  /** @format int32 */\n  movieYear?: number;\n}\n\nexport interface ImportListExclusionResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: ImportListExclusionResource[] | null;\n}\n\nexport interface ImportListResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: ImportListResource[] | null;\n  enabled?: boolean;\n  enableAuto?: boolean;\n  monitor?: MonitorTypes;\n  rootFolderPath?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number;\n  searchOnAdd?: boolean;\n  minimumAvailability?: MovieStatusType;\n  listType?: ImportListType;\n  /** @format int32 */\n  listOrder?: number;\n  /** @format date-span */\n  minRefreshInterval?: string;\n}\n\nexport interface ImportRejectionResource {\n  reason?: string | null;\n  type?: RejectionType;\n}\n\nexport interface IndexerBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enableRss?: boolean | null;\n  enableAutomaticSearch?: boolean | null;\n  enableInteractiveSearch?: boolean | null;\n  /** @format int32 */\n  priority?: number | null;\n}\n\nexport interface IndexerConfigResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  minimumAge?: number;\n  /** @format int32 */\n  maximumSize?: number;\n  /** @format int32 */\n  retention?: number;\n  /** @format int32 */\n  rssSyncInterval?: number;\n  preferIndexerFlags?: boolean;\n  /** @format int32 */\n  availabilityDelay?: number;\n  allowHardcodedSubs?: boolean;\n  whitelistedHardcodedSubs?: string | null;\n}\n\nexport interface IndexerFlagResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  nameLower?: string | null;\n}\n\nexport interface IndexerResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: IndexerResource[] | null;\n  enableRss?: boolean;\n  enableAutomaticSearch?: boolean;\n  enableInteractiveSearch?: boolean;\n  supportsRss?: boolean;\n  supportsSearch?: boolean;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  priority?: number;\n  /** @format int32 */\n  downloadClientId?: number;\n}\n\nexport interface Language {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n}\n\nexport interface LanguageResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  nameLower?: string | null;\n}\n\nexport interface LocalizationLanguageResource {\n  identifier?: string | null;\n}\n\nexport interface LogFileResource {\n  /** @format int32 */\n  id?: number;\n  filename?: string | null;\n  /** @format date-time */\n  lastWriteTime?: string;\n  contentsUrl?: string | null;\n  downloadUrl?: string | null;\n}\n\nexport interface LogResource {\n  /** @format int32 */\n  id?: number;\n  /** @format date-time */\n  time?: string;\n  exception?: string | null;\n  exceptionType?: string | null;\n  level?: string | null;\n  logger?: string | null;\n  message?: string | null;\n  method?: string | null;\n}\n\nexport interface LogResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: LogResource[] | null;\n}\n\nexport interface ManualImportReprocessResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  /** @format int32 */\n  movieId?: number;\n  movie?: MovieResource;\n  quality?: QualityModel;\n  languages?: Language[] | null;\n  releaseGroup?: string | null;\n  downloadId?: string | null;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  /** @format int32 */\n  indexerFlags?: number;\n  rejections?: ImportRejectionResource[] | null;\n}\n\nexport interface ManualImportResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  relativePath?: string | null;\n  folderName?: string | null;\n  name?: string | null;\n  /** @format int64 */\n  size?: number;\n  movie?: MovieResource;\n  /** @format int32 */\n  movieFileId?: number | null;\n  releaseGroup?: string | null;\n  quality?: QualityModel;\n  languages?: Language[] | null;\n  /** @format int32 */\n  qualityWeight?: number;\n  downloadId?: string | null;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  /** @format int32 */\n  indexerFlags?: number;\n  rejections?: ImportRejectionResource[] | null;\n}\n\nexport interface MediaCover {\n  coverType?: MediaCoverTypes;\n  url?: string | null;\n  remoteUrl?: string | null;\n}\n\nexport interface MediaInfoResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int64 */\n  audioBitrate?: number;\n  /** @format double */\n  audioChannels?: number;\n  audioCodec?: string | null;\n  audioLanguages?: string | null;\n  /** @format int32 */\n  audioStreamCount?: number;\n  /** @format int32 */\n  videoBitDepth?: number;\n  /** @format int64 */\n  videoBitrate?: number;\n  videoCodec?: string | null;\n  /** @format double */\n  videoFps?: number;\n  videoDynamicRange?: string | null;\n  videoDynamicRangeType?: string | null;\n  resolution?: string | null;\n  runTime?: string | null;\n  scanType?: string | null;\n  subtitles?: string | null;\n}\n\nexport interface MediaManagementConfigResource {\n  /** @format int32 */\n  id?: number;\n  autoUnmonitorPreviouslyDownloadedMovies?: boolean;\n  recycleBin?: string | null;\n  /** @format int32 */\n  recycleBinCleanupDays?: number;\n  downloadPropersAndRepacks?: ProperDownloadTypes;\n  createEmptyMovieFolders?: boolean;\n  deleteEmptyFolders?: boolean;\n  fileDate?: FileDateType;\n  rescanAfterRefresh?: RescanAfterRefreshType;\n  autoRenameFolders?: boolean;\n  pathsDefaultStatic?: boolean;\n  setPermissionsLinux?: boolean;\n  chmodFolder?: string | null;\n  chownGroup?: string | null;\n  skipFreeSpaceCheckWhenImporting?: boolean;\n  /** @format int32 */\n  minimumFreeSpaceWhenImporting?: number;\n  copyUsingHardlinks?: boolean;\n  useScriptImport?: boolean;\n  scriptImportPath?: string | null;\n  importExtraFiles?: boolean;\n  extraFileExtensions?: string | null;\n  enableMediaInfo?: boolean;\n}\n\nexport interface MetadataConfigResource {\n  /** @format int32 */\n  id?: number;\n  certificationCountry?: TMDbCountryCode;\n}\n\nexport interface MetadataResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: MetadataResource[] | null;\n  enable?: boolean;\n}\n\nexport interface MovieCollectionResource {\n  title?: string | null;\n  /** @format int32 */\n  tmdbId?: number;\n}\n\nexport interface MovieEditorResource {\n  movieIds?: number[] | null;\n  monitored?: boolean | null;\n  /** @format int32 */\n  qualityProfileId?: number | null;\n  minimumAvailability?: MovieStatusType;\n  rootFolderPath?: string | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  moveFiles?: boolean;\n  deleteFiles?: boolean;\n  addImportExclusion?: boolean;\n}\n\nexport interface MovieFileListResource {\n  movieFileIds?: number[] | null;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  edition?: string | null;\n  releaseGroup?: string | null;\n  sceneName?: string | null;\n  /** @format int32 */\n  indexerFlags?: number | null;\n}\n\nexport interface MovieFileResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  movieId?: number;\n  relativePath?: string | null;\n  path?: string | null;\n  /** @format int64 */\n  size?: number;\n  /** @format date-time */\n  dateAdded?: string;\n  sceneName?: string | null;\n  releaseGroup?: string | null;\n  edition?: string | null;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number | null;\n  /** @format int32 */\n  indexerFlags?: number | null;\n  mediaInfo?: MediaInfoResource;\n  originalFilePath?: string | null;\n  qualityCutoffNotMet?: boolean;\n}\n\nexport interface MovieResource {\n  /** @format int32 */\n  id?: number;\n  title?: string | null;\n  originalTitle?: string | null;\n  originalLanguage?: Language;\n  alternateTitles?: AlternativeTitleResource[] | null;\n  /** @format int32 */\n  secondaryYear?: number | null;\n  /** @format int32 */\n  secondaryYearSourceId?: number;\n  sortTitle?: string | null;\n  /** @format int64 */\n  sizeOnDisk?: number | null;\n  status?: MovieStatusType;\n  overview?: string | null;\n  /** @format date-time */\n  inCinemas?: string | null;\n  /** @format date-time */\n  physicalRelease?: string | null;\n  /** @format date-time */\n  digitalRelease?: string | null;\n  /** @format date-time */\n  releaseDate?: string | null;\n  physicalReleaseNote?: string | null;\n  images?: MediaCover[] | null;\n  website?: string | null;\n  remotePoster?: string | null;\n  /** @format int32 */\n  year?: number;\n  youTubeTrailerId?: string | null;\n  studio?: string | null;\n  path?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number;\n  hasFile?: boolean | null;\n  /** @format int32 */\n  movieFileId?: number;\n  monitored?: boolean;\n  minimumAvailability?: MovieStatusType;\n  isAvailable?: boolean;\n  folderName?: string | null;\n  /** @format int32 */\n  runtime?: number;\n  cleanTitle?: string | null;\n  imdbId?: string | null;\n  /** @format int32 */\n  tmdbId?: number;\n  titleSlug?: string | null;\n  rootFolderPath?: string | null;\n  folder?: string | null;\n  certification?: string | null;\n  genres?: string[] | null;\n  keywords?: string[] | null;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  /** @format date-time */\n  added?: string;\n  addOptions?: AddMovieOptions;\n  ratings?: Ratings;\n  movieFile?: MovieFileResource;\n  collection?: MovieCollectionResource;\n  /** @format float */\n  popularity?: number;\n  /** @format date-time */\n  lastSearchTime?: string | null;\n  statistics?: MovieStatisticsResource;\n}\n\nexport interface MovieResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: MovieResource[] | null;\n}\n\nexport interface MovieStatisticsResource {\n  /** @format int32 */\n  movieFileCount?: number;\n  /** @format int64 */\n  sizeOnDisk?: number;\n  releaseGroups?: string[] | null;\n}\n\nexport interface NamingConfigResource {\n  /** @format int32 */\n  id?: number;\n  renameMovies?: boolean;\n  replaceIllegalCharacters?: boolean;\n  colonReplacementFormat?: ColonReplacementFormat;\n  standardMovieFormat?: string | null;\n  movieFolderFormat?: string | null;\n}\n\nexport interface NotificationResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: NotificationResource[] | null;\n  link?: string | null;\n  onGrab?: boolean;\n  onDownload?: boolean;\n  onUpgrade?: boolean;\n  onRename?: boolean;\n  onMovieAdded?: boolean;\n  onMovieDelete?: boolean;\n  onMovieFileDelete?: boolean;\n  onMovieFileDeleteForUpgrade?: boolean;\n  onHealthIssue?: boolean;\n  includeHealthWarnings?: boolean;\n  onHealthRestored?: boolean;\n  onApplicationUpdate?: boolean;\n  onManualInteractionRequired?: boolean;\n  supportsOnGrab?: boolean;\n  supportsOnDownload?: boolean;\n  supportsOnUpgrade?: boolean;\n  supportsOnRename?: boolean;\n  supportsOnMovieAdded?: boolean;\n  supportsOnMovieDelete?: boolean;\n  supportsOnMovieFileDelete?: boolean;\n  supportsOnMovieFileDeleteForUpgrade?: boolean;\n  supportsOnHealthIssue?: boolean;\n  supportsOnHealthRestored?: boolean;\n  supportsOnApplicationUpdate?: boolean;\n  supportsOnManualInteractionRequired?: boolean;\n  testCommand?: string | null;\n}\n\nexport interface ParseResource {\n  /** @format int32 */\n  id?: number;\n  title?: string | null;\n  parsedMovieInfo?: ParsedMovieInfo;\n  movie?: MovieResource;\n  languages?: Language[] | null;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n}\n\nexport interface ParsedMovieInfo {\n  movieTitles?: string[] | null;\n  originalTitle?: string | null;\n  releaseTitle?: string | null;\n  simpleReleaseTitle?: string | null;\n  quality?: QualityModel;\n  languages?: Language[] | null;\n  releaseGroup?: string | null;\n  releaseHash?: string | null;\n  edition?: string | null;\n  /** @format int32 */\n  year?: number;\n  imdbId?: string | null;\n  /** @format int32 */\n  tmdbId?: number;\n  hardcodedSubs?: string | null;\n  movieTitle?: string | null;\n  primaryMovieTitle?: string | null;\n}\n\nexport interface PingResource {\n  status?: string | null;\n}\n\nexport interface ProfileFormatItemResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  format?: number;\n  name?: string | null;\n  /** @format int32 */\n  score?: number;\n}\n\nexport interface ProviderMessage {\n  message?: string | null;\n  type?: ProviderMessageType;\n}\n\nexport interface Quality {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  source?: QualitySource;\n  /** @format int32 */\n  resolution?: number;\n  modifier?: Modifier;\n}\n\nexport interface QualityDefinitionLimitsResource {\n  /** @format int32 */\n  min?: number;\n  /** @format int32 */\n  max?: number;\n}\n\nexport interface QualityDefinitionResource {\n  /** @format int32 */\n  id?: number;\n  quality?: Quality;\n  title?: string | null;\n  /** @format int32 */\n  weight?: number;\n  /** @format double */\n  minSize?: number | null;\n  /** @format double */\n  maxSize?: number | null;\n  /** @format double */\n  preferredSize?: number | null;\n}\n\nexport interface QualityModel {\n  quality?: Quality;\n  revision?: Revision;\n}\n\nexport interface QualityProfileQualityItemResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  quality?: Quality;\n  items?: QualityProfileQualityItemResource[] | null;\n  allowed?: boolean;\n}\n\nexport interface QualityProfileResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  upgradeAllowed?: boolean;\n  /** @format int32 */\n  cutoff?: number;\n  items?: QualityProfileQualityItemResource[] | null;\n  /** @format int32 */\n  minFormatScore?: number;\n  /** @format int32 */\n  cutoffFormatScore?: number;\n  /** @format int32 */\n  minUpgradeFormatScore?: number;\n  formatItems?: ProfileFormatItemResource[] | null;\n  language?: Language;\n}\n\nexport interface QueueBulkResource {\n  ids?: number[] | null;\n}\n\nexport interface QueueResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  movieId?: number | null;\n  movie?: MovieResource;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  /** @format double */\n  size?: number;\n  title?: string | null;\n  /** @format date-time */\n  estimatedCompletionTime?: string | null;\n  /** @format date-time */\n  added?: string | null;\n  status?: QueueStatus;\n  trackedDownloadStatus?: TrackedDownloadStatus;\n  trackedDownloadState?: TrackedDownloadState;\n  statusMessages?: TrackedDownloadStatusMessage[] | null;\n  errorMessage?: string | null;\n  downloadId?: string | null;\n  protocol?: DownloadProtocol;\n  downloadClient?: string | null;\n  downloadClientHasPostImportCategory?: boolean;\n  indexer?: string | null;\n  outputPath?: string | null;\n  /**\n   * @deprecated\n   * @format double\n   */\n  sizeleft?: number;\n  /**\n   * @deprecated\n   * @format date-span\n   */\n  timeleft?: string | null;\n}\n\nexport interface QueueResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: QueueResource[] | null;\n}\n\nexport interface QueueStatusResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  totalCount?: number;\n  /** @format int32 */\n  count?: number;\n  /** @format int32 */\n  unknownCount?: number;\n  errors?: boolean;\n  warnings?: boolean;\n  unknownErrors?: boolean;\n  unknownWarnings?: boolean;\n}\n\nexport interface RatingChild {\n  /** @format int32 */\n  votes?: number;\n  /** @format double */\n  value?: number;\n  type?: RatingType;\n}\n\nexport interface Ratings {\n  imdb?: RatingChild;\n  tmdb?: RatingChild;\n  metacritic?: RatingChild;\n  rottenTomatoes?: RatingChild;\n  trakt?: RatingChild;\n}\n\nexport interface ReleaseProfileResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  enabled?: boolean;\n  required?: any;\n  ignored?: any;\n  /** @format int32 */\n  indexerId?: number;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n}\n\nexport interface ReleaseResource {\n  /** @format int32 */\n  id?: number;\n  guid?: string | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  /** @format int32 */\n  qualityWeight?: number;\n  /** @format int32 */\n  age?: number;\n  /** @format double */\n  ageHours?: number;\n  /** @format double */\n  ageMinutes?: number;\n  /** @format int64 */\n  size?: number;\n  /** @format int32 */\n  indexerId?: number;\n  indexer?: string | null;\n  releaseGroup?: string | null;\n  subGroup?: string | null;\n  releaseHash?: string | null;\n  title?: string | null;\n  sceneSource?: boolean;\n  movieTitles?: string[] | null;\n  languages?: Language[] | null;\n  /** @format int32 */\n  mappedMovieId?: number | null;\n  approved?: boolean;\n  temporarilyRejected?: boolean;\n  rejected?: boolean;\n  /** @format int32 */\n  tmdbId?: number;\n  /** @format int32 */\n  imdbId?: number;\n  rejections?: string[] | null;\n  /** @format date-time */\n  publishDate?: string;\n  commentUrl?: string | null;\n  downloadUrl?: string | null;\n  infoUrl?: string | null;\n  movieRequested?: boolean;\n  downloadAllowed?: boolean;\n  /** @format int32 */\n  releaseWeight?: number;\n  edition?: string | null;\n  magnetUrl?: string | null;\n  infoHash?: string | null;\n  /** @format int32 */\n  seeders?: number | null;\n  /** @format int32 */\n  leechers?: number | null;\n  protocol?: DownloadProtocol;\n  indexerFlags?: any;\n  /** @format int32 */\n  movieId?: number | null;\n  /** @format int32 */\n  downloadClientId?: number | null;\n  downloadClient?: string | null;\n  shouldOverride?: boolean | null;\n}\n\nexport interface RemotePathMappingResource {\n  /** @format int32 */\n  id?: number;\n  host?: string | null;\n  remotePath?: string | null;\n  localPath?: string | null;\n}\n\nexport interface RenameMovieResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  movieId?: number;\n  /** @format int32 */\n  movieFileId?: number;\n  existingPath?: string | null;\n  newPath?: string | null;\n}\n\nexport interface Revision {\n  /** @format int32 */\n  version?: number;\n  /** @format int32 */\n  real?: number;\n  isRepack?: boolean;\n}\n\nexport interface RootFolderResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  accessible?: boolean;\n  /** @format int64 */\n  freeSpace?: number | null;\n  unmappedFolders?: UnmappedFolder[] | null;\n}\n\nexport interface SelectOption {\n  /** @format int32 */\n  value?: number;\n  name?: string | null;\n  /** @format int32 */\n  order?: number;\n  hint?: string | null;\n  dividerAfter?: boolean;\n}\n\nexport interface SystemResource {\n  appName?: string | null;\n  instanceName?: string | null;\n  version?: string | null;\n  /** @format date-time */\n  buildTime?: string;\n  isDebug?: boolean;\n  isProduction?: boolean;\n  isAdmin?: boolean;\n  isUserInteractive?: boolean;\n  startupPath?: string | null;\n  appData?: string | null;\n  osName?: string | null;\n  osVersion?: string | null;\n  isNetCore?: boolean;\n  isLinux?: boolean;\n  isOsx?: boolean;\n  isWindows?: boolean;\n  isDocker?: boolean;\n  mode?: RuntimeMode;\n  branch?: string | null;\n  databaseType?: DatabaseType;\n  databaseVersion?: string | null;\n  authentication?: AuthenticationType;\n  /** @format int32 */\n  migrationVersion?: number;\n  urlBase?: string | null;\n  runtimeVersion?: string | null;\n  runtimeName?: string | null;\n  /** @format date-time */\n  startTime?: string;\n  packageVersion?: string | null;\n  packageAuthor?: string | null;\n  packageUpdateMechanism?: UpdateMechanism;\n  packageUpdateMechanismMessage?: string | null;\n}\n\nexport interface TagDetailsResource {\n  /** @format int32 */\n  id?: number;\n  label?: string | null;\n  delayProfileIds?: number[] | null;\n  importListIds?: number[] | null;\n  notificationIds?: number[] | null;\n  releaseProfileIds?: number[] | null;\n  indexerIds?: number[] | null;\n  downloadClientIds?: number[] | null;\n  autoTagIds?: number[] | null;\n  movieIds?: number[] | null;\n}\n\nexport interface TagResource {\n  /** @format int32 */\n  id?: number;\n  label?: string | null;\n}\n\nexport interface TaskResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  taskName?: string | null;\n  /** @format int32 */\n  interval?: number;\n  /** @format date-time */\n  lastExecution?: string;\n  /** @format date-time */\n  lastStartTime?: string;\n  /** @format date-time */\n  nextExecution?: string;\n  /** @format date-span */\n  lastDuration?: string;\n}\n\nexport interface TrackedDownloadStatusMessage {\n  title?: string | null;\n  messages?: string[] | null;\n}\n\nexport interface UiConfigResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  firstDayOfWeek?: number;\n  calendarWeekColumnHeader?: string | null;\n  movieRuntimeFormat?: MovieRuntimeFormatType;\n  shortDateFormat?: string | null;\n  longDateFormat?: string | null;\n  timeFormat?: string | null;\n  showRelativeDates?: boolean;\n  enableColorImpairedMode?: boolean;\n  /** @format int32 */\n  movieInfoLanguage?: number;\n  /** @format int32 */\n  uiLanguage?: number;\n  theme?: string | null;\n}\n\nexport interface UnmappedFolder {\n  name?: string | null;\n  path?: string | null;\n  relativePath?: string | null;\n}\n\nexport interface UpdateChanges {\n  new?: string[] | null;\n  fixed?: string[] | null;\n}\n\nexport interface UpdateResource {\n  /** @format int32 */\n  id?: number;\n  version?: string | null;\n  branch?: string | null;\n  /** @format date-time */\n  releaseDate?: string;\n  fileName?: string | null;\n  url?: string | null;\n  installed?: boolean;\n  /** @format date-time */\n  installedOn?: string | null;\n  installable?: boolean;\n  latest?: boolean;\n  changes?: UpdateChanges;\n  hash?: string | null;\n}\n"
  },
  {
    "path": "src/__generated__/readarr/Api.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { ContentType, HttpClient, RequestParams } from \"./../../ky-client\";\nimport {\n  ApiInfoResource,\n  AuthorEditorResource,\n  AuthorResource,\n  BackupResource,\n  BlocklistBulkResource,\n  BlocklistResourcePagingResource,\n  BookEditorResource,\n  BookFileListResource,\n  BookFileResource,\n  BookResource,\n  BookResourcePagingResource,\n  BooksMonitoredResource,\n  BookshelfResource,\n  CommandResource,\n  CustomFilterResource,\n  CustomFormatResource,\n  DelayProfileResource,\n  DevelopmentConfigResource,\n  DiskSpaceResource,\n  DownloadClientBulkResource,\n  DownloadClientConfigResource,\n  DownloadClientResource,\n  EditionResource,\n  EntityHistoryEventType,\n  HealthResource,\n  HistoryResource,\n  HistoryResourcePagingResource,\n  HostConfigResource,\n  ImportListBulkResource,\n  ImportListExclusionResource,\n  ImportListResource,\n  IndexerBulkResource,\n  IndexerConfigResource,\n  IndexerFlagResource,\n  IndexerResource,\n  LanguageResource,\n  LogFileResource,\n  LogResourcePagingResource,\n  ManualImportResource,\n  ManualImportUpdateResource,\n  MediaManagementConfigResource,\n  MetadataProfileResource,\n  MetadataProviderConfigResource,\n  MetadataResource,\n  NamingConfigResource,\n  NotificationResource,\n  ParseResource,\n  QualityDefinitionResource,\n  QualityProfileResource,\n  QueueBulkResource,\n  QueueResource,\n  QueueResourcePagingResource,\n  QueueStatusResource,\n  ReleaseProfileResource,\n  ReleaseResource,\n  RemotePathMappingResource,\n  RenameBookResource,\n  RetagBookResource,\n  RootFolderResource,\n  SeriesResource,\n  SortDirection,\n  SystemResource,\n  TagDetailsResource,\n  TagResource,\n  TaskResource,\n  UiConfigResource,\n  UpdateResource,\n} from \"./data-contracts\";\n\nexport class Api<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags ApiInfo\n   * @name GetApi\n   * @request GET:/api\n   * @secure\n   */\n  getApi = (params: RequestParams = {}) =>\n    this.http.request<ApiInfoResource, any>({\n      path: `/api`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Author\n   * @name V1AuthorList\n   * @request GET:/api/v1/author\n   * @secure\n   */\n  v1AuthorList = (params: RequestParams = {}) =>\n    this.http.request<AuthorResource[], any>({\n      path: `/api/v1/author`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Author\n   * @name V1AuthorCreate\n   * @request POST:/api/v1/author\n   * @secure\n   */\n  v1AuthorCreate = (data: AuthorResource, params: RequestParams = {}) =>\n    this.http.request<AuthorResource, any>({\n      path: `/api/v1/author`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Author\n   * @name V1AuthorUpdate\n   * @request PUT:/api/v1/author/{id}\n   * @secure\n   */\n  v1AuthorUpdate = (\n    id: string,\n    data: AuthorResource,\n    query?: {\n      /** @default false */\n      moveFiles?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<AuthorResource, any>({\n      path: `/api/v1/author/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Author\n   * @name V1AuthorDelete\n   * @request DELETE:/api/v1/author/{id}\n   * @secure\n   */\n  v1AuthorDelete = (\n    id: number,\n    query?: {\n      /** @default false */\n      deleteFiles?: boolean;\n      /** @default false */\n      addImportListExclusion?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/author/${id}`,\n      method: \"DELETE\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Author\n   * @name V1AuthorDetail\n   * @request GET:/api/v1/author/{id}\n   * @secure\n   */\n  v1AuthorDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<AuthorResource, any>({\n      path: `/api/v1/author/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AuthorEditor\n   * @name V1AuthorEditorUpdate\n   * @request PUT:/api/v1/author/editor\n   * @secure\n   */\n  v1AuthorEditorUpdate = (data: AuthorEditorResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/author/editor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AuthorEditor\n   * @name V1AuthorEditorDelete\n   * @request DELETE:/api/v1/author/editor\n   * @secure\n   */\n  v1AuthorEditorDelete = (data: AuthorEditorResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/author/editor`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AuthorLookup\n   * @name V1AuthorLookupList\n   * @request GET:/api/v1/author/lookup\n   * @secure\n   */\n  v1AuthorLookupList = (\n    query?: {\n      term?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/author/lookup`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V1SystemBackupList\n   * @request GET:/api/v1/system/backup\n   * @secure\n   */\n  v1SystemBackupList = (params: RequestParams = {}) =>\n    this.http.request<BackupResource[], any>({\n      path: `/api/v1/system/backup`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V1SystemBackupDelete\n   * @request DELETE:/api/v1/system/backup/{id}\n   * @secure\n   */\n  v1SystemBackupDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/backup/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V1SystemBackupRestoreCreate\n   * @request POST:/api/v1/system/backup/restore/{id}\n   * @secure\n   */\n  v1SystemBackupRestoreCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/backup/restore/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V1SystemBackupRestoreUploadCreate\n   * @request POST:/api/v1/system/backup/restore/upload\n   * @secure\n   */\n  v1SystemBackupRestoreUploadCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/backup/restore/upload`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V1BlocklistList\n   * @request GET:/api/v1/blocklist\n   * @secure\n   */\n  v1BlocklistList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<BlocklistResourcePagingResource, any>({\n      path: `/api/v1/blocklist`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V1BlocklistDelete\n   * @request DELETE:/api/v1/blocklist/{id}\n   * @secure\n   */\n  v1BlocklistDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/blocklist/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V1BlocklistBulkDelete\n   * @request DELETE:/api/v1/blocklist/bulk\n   * @secure\n   */\n  v1BlocklistBulkDelete = (data: BlocklistBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/blocklist/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Book\n   * @name V1BookList\n   * @request GET:/api/v1/book\n   * @secure\n   */\n  v1BookList = (\n    query?: {\n      /** @format int32 */\n      authorId?: number;\n      bookIds?: number[];\n      titleSlug?: string;\n      /** @default false */\n      includeAllAuthorBooks?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<BookResource[], any>({\n      path: `/api/v1/book`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Book\n   * @name V1BookCreate\n   * @request POST:/api/v1/book\n   * @secure\n   */\n  v1BookCreate = (data: BookResource, params: RequestParams = {}) =>\n    this.http.request<BookResource, any>({\n      path: `/api/v1/book`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Book\n   * @name V1BookOverviewList\n   * @request GET:/api/v1/book/{id}/overview\n   * @secure\n   */\n  v1BookOverviewList = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/book/${id}/overview`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Book\n   * @name V1BookUpdate\n   * @request PUT:/api/v1/book/{id}\n   * @secure\n   */\n  v1BookUpdate = (id: string, data: BookResource, params: RequestParams = {}) =>\n    this.http.request<BookResource, any>({\n      path: `/api/v1/book/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Book\n   * @name V1BookDelete\n   * @request DELETE:/api/v1/book/{id}\n   * @secure\n   */\n  v1BookDelete = (\n    id: number,\n    query?: {\n      /** @default false */\n      deleteFiles?: boolean;\n      /** @default false */\n      addImportListExclusion?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/book/${id}`,\n      method: \"DELETE\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Book\n   * @name V1BookDetail\n   * @request GET:/api/v1/book/{id}\n   * @secure\n   */\n  v1BookDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<BookResource, any>({\n      path: `/api/v1/book/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Book\n   * @name V1BookMonitorUpdate\n   * @request PUT:/api/v1/book/monitor\n   * @secure\n   */\n  v1BookMonitorUpdate = (data: BooksMonitoredResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/book/monitor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags BookEditor\n   * @name V1BookEditorUpdate\n   * @request PUT:/api/v1/book/editor\n   * @secure\n   */\n  v1BookEditorUpdate = (data: BookEditorResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/book/editor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags BookEditor\n   * @name V1BookEditorDelete\n   * @request DELETE:/api/v1/book/editor\n   * @secure\n   */\n  v1BookEditorDelete = (data: BookEditorResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/book/editor`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags BookFile\n   * @name V1BookfileList\n   * @request GET:/api/v1/bookfile\n   * @secure\n   */\n  v1BookfileList = (\n    query?: {\n      /** @format int32 */\n      authorId?: number;\n      bookFileIds?: number[];\n      bookId?: number[];\n      unmapped?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<BookFileResource[], any>({\n      path: `/api/v1/bookfile`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags BookFile\n   * @name V1BookfileUpdate\n   * @request PUT:/api/v1/bookfile/{id}\n   * @secure\n   */\n  v1BookfileUpdate = (id: string, data: BookFileResource, params: RequestParams = {}) =>\n    this.http.request<BookFileResource, any>({\n      path: `/api/v1/bookfile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags BookFile\n   * @name V1BookfileDelete\n   * @request DELETE:/api/v1/bookfile/{id}\n   * @secure\n   */\n  v1BookfileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/bookfile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags BookFile\n   * @name V1BookfileDetail\n   * @request GET:/api/v1/bookfile/{id}\n   * @secure\n   */\n  v1BookfileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<BookFileResource, any>({\n      path: `/api/v1/bookfile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags BookFile\n   * @name V1BookfileEditorUpdate\n   * @request PUT:/api/v1/bookfile/editor\n   * @secure\n   */\n  v1BookfileEditorUpdate = (data: BookFileListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/bookfile/editor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags BookFile\n   * @name V1BookfileBulkDelete\n   * @request DELETE:/api/v1/bookfile/bulk\n   * @secure\n   */\n  v1BookfileBulkDelete = (data: BookFileListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/bookfile/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags BookLookup\n   * @name V1BookLookupList\n   * @request GET:/api/v1/book/lookup\n   * @secure\n   */\n  v1BookLookupList = (\n    query?: {\n      term?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/book/lookup`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Bookshelf\n   * @name V1BookshelfCreate\n   * @request POST:/api/v1/bookshelf\n   * @secure\n   */\n  v1BookshelfCreate = (data: BookshelfResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/bookshelf`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Calendar\n   * @name V1CalendarList\n   * @request GET:/api/v1/calendar\n   * @secure\n   */\n  v1CalendarList = (\n    query?: {\n      /** @format date-time */\n      start?: string;\n      /** @format date-time */\n      end?: string;\n      /** @default false */\n      unmonitored?: boolean;\n      /** @default false */\n      includeAuthor?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<BookResource[], any>({\n      path: `/api/v1/calendar`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Calendar\n   * @name V1CalendarDetail\n   * @request GET:/api/v1/calendar/{id}\n   * @secure\n   */\n  v1CalendarDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<BookResource, any>({\n      path: `/api/v1/calendar/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V1CommandCreate\n   * @request POST:/api/v1/command\n   * @secure\n   */\n  v1CommandCreate = (data: CommandResource, params: RequestParams = {}) =>\n    this.http.request<CommandResource, any>({\n      path: `/api/v1/command`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V1CommandList\n   * @request GET:/api/v1/command\n   * @secure\n   */\n  v1CommandList = (params: RequestParams = {}) =>\n    this.http.request<CommandResource[], any>({\n      path: `/api/v1/command`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V1CommandDelete\n   * @request DELETE:/api/v1/command/{id}\n   * @secure\n   */\n  v1CommandDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/command/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V1CommandDetail\n   * @request GET:/api/v1/command/{id}\n   * @secure\n   */\n  v1CommandDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CommandResource, any>({\n      path: `/api/v1/command/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V1CustomfilterList\n   * @request GET:/api/v1/customfilter\n   * @secure\n   */\n  v1CustomfilterList = (params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource[], any>({\n      path: `/api/v1/customfilter`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V1CustomfilterCreate\n   * @request POST:/api/v1/customfilter\n   * @secure\n   */\n  v1CustomfilterCreate = (data: CustomFilterResource, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v1/customfilter`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V1CustomfilterUpdate\n   * @request PUT:/api/v1/customfilter/{id}\n   * @secure\n   */\n  v1CustomfilterUpdate = (id: string, data: CustomFilterResource, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v1/customfilter/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V1CustomfilterDelete\n   * @request DELETE:/api/v1/customfilter/{id}\n   * @secure\n   */\n  v1CustomfilterDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/customfilter/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V1CustomfilterDetail\n   * @request GET:/api/v1/customfilter/{id}\n   * @secure\n   */\n  v1CustomfilterDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v1/customfilter/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatCreate\n   * @request POST:/api/v1/customformat\n   * @secure\n   */\n  v1CustomformatCreate = (data: CustomFormatResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v1/customformat`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatList\n   * @request GET:/api/v1/customformat\n   * @secure\n   */\n  v1CustomformatList = (params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource[], any>({\n      path: `/api/v1/customformat`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatUpdate\n   * @request PUT:/api/v1/customformat/{id}\n   * @secure\n   */\n  v1CustomformatUpdate = (id: string, data: CustomFormatResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v1/customformat/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatDelete\n   * @request DELETE:/api/v1/customformat/{id}\n   * @secure\n   */\n  v1CustomformatDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/customformat/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatDetail\n   * @request GET:/api/v1/customformat/{id}\n   * @secure\n   */\n  v1CustomformatDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v1/customformat/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V1CustomformatSchemaList\n   * @request GET:/api/v1/customformat/schema\n   * @secure\n   */\n  v1CustomformatSchemaList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/customformat/schema`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Cutoff\n   * @name V1WantedCutoffList\n   * @request GET:/api/v1/wanted/cutoff\n   * @secure\n   */\n  v1WantedCutoffList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeAuthor?: boolean;\n      /** @default true */\n      monitored?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<BookResourcePagingResource, any>({\n      path: `/api/v1/wanted/cutoff`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Cutoff\n   * @name V1WantedCutoffDetail\n   * @request GET:/api/v1/wanted/cutoff/{id}\n   * @secure\n   */\n  v1WantedCutoffDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<BookResource, any>({\n      path: `/api/v1/wanted/cutoff/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V1DelayprofileCreate\n   * @request POST:/api/v1/delayprofile\n   * @secure\n   */\n  v1DelayprofileCreate = (data: DelayProfileResource, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v1/delayprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V1DelayprofileList\n   * @request GET:/api/v1/delayprofile\n   * @secure\n   */\n  v1DelayprofileList = (params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource[], any>({\n      path: `/api/v1/delayprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V1DelayprofileDelete\n   * @request DELETE:/api/v1/delayprofile/{id}\n   * @secure\n   */\n  v1DelayprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/delayprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V1DelayprofileUpdate\n   * @request PUT:/api/v1/delayprofile/{id}\n   * @secure\n   */\n  v1DelayprofileUpdate = (id: string, data: DelayProfileResource, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v1/delayprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V1DelayprofileDetail\n   * @request GET:/api/v1/delayprofile/{id}\n   * @secure\n   */\n  v1DelayprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v1/delayprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V1DelayprofileReorderUpdate\n   * @request PUT:/api/v1/delayprofile/reorder/{id}\n   * @secure\n   */\n  v1DelayprofileReorderUpdate = (\n    id: number,\n    query?: {\n      /** @format int32 */\n      afterId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/delayprofile/reorder/${id}`,\n      method: \"PUT\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DevelopmentConfig\n   * @name V1ConfigDevelopmentList\n   * @request GET:/api/v1/config/development\n   * @secure\n   */\n  v1ConfigDevelopmentList = (params: RequestParams = {}) =>\n    this.http.request<DevelopmentConfigResource, any>({\n      path: `/api/v1/config/development`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DevelopmentConfig\n   * @name V1ConfigDevelopmentUpdate\n   * @request PUT:/api/v1/config/development/{id}\n   * @secure\n   */\n  v1ConfigDevelopmentUpdate = (id: string, data: DevelopmentConfigResource, params: RequestParams = {}) =>\n    this.http.request<DevelopmentConfigResource, any>({\n      path: `/api/v1/config/development/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DevelopmentConfig\n   * @name V1ConfigDevelopmentDetail\n   * @request GET:/api/v1/config/development/{id}\n   * @secure\n   */\n  v1ConfigDevelopmentDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DevelopmentConfigResource, any>({\n      path: `/api/v1/config/development/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DiskSpace\n   * @name V1DiskspaceList\n   * @request GET:/api/v1/diskspace\n   * @secure\n   */\n  v1DiskspaceList = (params: RequestParams = {}) =>\n    this.http.request<DiskSpaceResource[], any>({\n      path: `/api/v1/diskspace`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientList\n   * @request GET:/api/v1/downloadclient\n   * @secure\n   */\n  v1DownloadclientList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource[], any>({\n      path: `/api/v1/downloadclient`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientCreate\n   * @request POST:/api/v1/downloadclient\n   * @secure\n   */\n  v1DownloadclientCreate = (\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v1/downloadclient`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientUpdate\n   * @request PUT:/api/v1/downloadclient/{id}\n   * @secure\n   */\n  v1DownloadclientUpdate = (\n    id: string,\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v1/downloadclient/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientDelete\n   * @request DELETE:/api/v1/downloadclient/{id}\n   * @secure\n   */\n  v1DownloadclientDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/downloadclient/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientDetail\n   * @request GET:/api/v1/downloadclient/{id}\n   * @secure\n   */\n  v1DownloadclientDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v1/downloadclient/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientBulkUpdate\n   * @request PUT:/api/v1/downloadclient/bulk\n   * @secure\n   */\n  v1DownloadclientBulkUpdate = (data: DownloadClientBulkResource, params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v1/downloadclient/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientBulkDelete\n   * @request DELETE:/api/v1/downloadclient/bulk\n   * @secure\n   */\n  v1DownloadclientBulkDelete = (data: DownloadClientBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/downloadclient/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientSchemaList\n   * @request GET:/api/v1/downloadclient/schema\n   * @secure\n   */\n  v1DownloadclientSchemaList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource[], any>({\n      path: `/api/v1/downloadclient/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientTestCreate\n   * @request POST:/api/v1/downloadclient/test\n   * @secure\n   */\n  v1DownloadclientTestCreate = (\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/downloadclient/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientTestallCreate\n   * @request POST:/api/v1/downloadclient/testall\n   * @secure\n   */\n  v1DownloadclientTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/downloadclient/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V1DownloadclientActionCreate\n   * @request POST:/api/v1/downloadclient/action/{name}\n   * @secure\n   */\n  v1DownloadclientActionCreate = (name: string, data: DownloadClientResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/downloadclient/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V1ConfigDownloadclientList\n   * @request GET:/api/v1/config/downloadclient\n   * @secure\n   */\n  v1ConfigDownloadclientList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v1/config/downloadclient`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V1ConfigDownloadclientUpdate\n   * @request PUT:/api/v1/config/downloadclient/{id}\n   * @secure\n   */\n  v1ConfigDownloadclientUpdate = (id: string, data: DownloadClientConfigResource, params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v1/config/downloadclient/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V1ConfigDownloadclientDetail\n   * @request GET:/api/v1/config/downloadclient/{id}\n   * @secure\n   */\n  v1ConfigDownloadclientDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v1/config/downloadclient/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Edition\n   * @name V1EditionList\n   * @request GET:/api/v1/edition\n   * @secure\n   */\n  v1EditionList = (\n    query?: {\n      bookId?: number[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<EditionResource[], any>({\n      path: `/api/v1/edition`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V1FilesystemList\n   * @request GET:/api/v1/filesystem\n   * @secure\n   */\n  v1FilesystemList = (\n    query?: {\n      path?: string;\n      /** @default false */\n      includeFiles?: boolean;\n      /** @default false */\n      allowFoldersWithoutTrailingSlashes?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/filesystem`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V1FilesystemTypeList\n   * @request GET:/api/v1/filesystem/type\n   * @secure\n   */\n  v1FilesystemTypeList = (\n    query?: {\n      path?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/filesystem/type`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V1FilesystemMediafilesList\n   * @request GET:/api/v1/filesystem/mediafiles\n   * @secure\n   */\n  v1FilesystemMediafilesList = (\n    query?: {\n      path?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/filesystem/mediafiles`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Health\n   * @name V1HealthList\n   * @request GET:/api/v1/health\n   * @secure\n   */\n  v1HealthList = (params: RequestParams = {}) =>\n    this.http.request<HealthResource[], any>({\n      path: `/api/v1/health`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V1HistoryList\n   * @request GET:/api/v1/history\n   * @secure\n   */\n  v1HistoryList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      includeAuthor?: boolean;\n      includeBook?: boolean;\n      eventType?: number[];\n      /** @format int32 */\n      bookId?: number;\n      downloadId?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResourcePagingResource, any>({\n      path: `/api/v1/history`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V1HistorySinceList\n   * @request GET:/api/v1/history/since\n   * @secure\n   */\n  v1HistorySinceList = (\n    query?: {\n      /** @format date-time */\n      date?: string;\n      eventType?: EntityHistoryEventType;\n      /** @default false */\n      includeAuthor?: boolean;\n      /** @default false */\n      includeBook?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResource[], any>({\n      path: `/api/v1/history/since`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V1HistoryAuthorList\n   * @request GET:/api/v1/history/author\n   * @secure\n   */\n  v1HistoryAuthorList = (\n    query?: {\n      /** @format int32 */\n      authorId?: number;\n      /** @format int32 */\n      bookId?: number;\n      eventType?: EntityHistoryEventType;\n      /** @default false */\n      includeAuthor?: boolean;\n      /** @default false */\n      includeBook?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResource[], any>({\n      path: `/api/v1/history/author`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V1HistoryFailedCreate\n   * @request POST:/api/v1/history/failed/{id}\n   * @secure\n   */\n  v1HistoryFailedCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/history/failed/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V1ConfigHostList\n   * @request GET:/api/v1/config/host\n   * @secure\n   */\n  v1ConfigHostList = (params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v1/config/host`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V1ConfigHostUpdate\n   * @request PUT:/api/v1/config/host/{id}\n   * @secure\n   */\n  v1ConfigHostUpdate = (id: string, data: HostConfigResource, params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v1/config/host/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V1ConfigHostDetail\n   * @request GET:/api/v1/config/host/{id}\n   * @secure\n   */\n  v1ConfigHostDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v1/config/host/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistList\n   * @request GET:/api/v1/importlist\n   * @secure\n   */\n  v1ImportlistList = (params: RequestParams = {}) =>\n    this.http.request<ImportListResource[], any>({\n      path: `/api/v1/importlist`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistCreate\n   * @request POST:/api/v1/importlist\n   * @secure\n   */\n  v1ImportlistCreate = (\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v1/importlist`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistUpdate\n   * @request PUT:/api/v1/importlist/{id}\n   * @secure\n   */\n  v1ImportlistUpdate = (\n    id: string,\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v1/importlist/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistDelete\n   * @request DELETE:/api/v1/importlist/{id}\n   * @secure\n   */\n  v1ImportlistDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/importlist/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistDetail\n   * @request GET:/api/v1/importlist/{id}\n   * @secure\n   */\n  v1ImportlistDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v1/importlist/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistBulkUpdate\n   * @request PUT:/api/v1/importlist/bulk\n   * @secure\n   */\n  v1ImportlistBulkUpdate = (data: ImportListBulkResource, params: RequestParams = {}) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v1/importlist/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistBulkDelete\n   * @request DELETE:/api/v1/importlist/bulk\n   * @secure\n   */\n  v1ImportlistBulkDelete = (data: ImportListBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/importlist/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistSchemaList\n   * @request GET:/api/v1/importlist/schema\n   * @secure\n   */\n  v1ImportlistSchemaList = (params: RequestParams = {}) =>\n    this.http.request<ImportListResource[], any>({\n      path: `/api/v1/importlist/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistTestCreate\n   * @request POST:/api/v1/importlist/test\n   * @secure\n   */\n  v1ImportlistTestCreate = (\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/importlist/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistTestallCreate\n   * @request POST:/api/v1/importlist/testall\n   * @secure\n   */\n  v1ImportlistTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/importlist/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V1ImportlistActionCreate\n   * @request POST:/api/v1/importlist/action/{name}\n   * @secure\n   */\n  v1ImportlistActionCreate = (name: string, data: ImportListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/importlist/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V1ImportlistexclusionList\n   * @request GET:/api/v1/importlistexclusion\n   * @secure\n   */\n  v1ImportlistexclusionList = (params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource[], any>({\n      path: `/api/v1/importlistexclusion`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V1ImportlistexclusionCreate\n   * @request POST:/api/v1/importlistexclusion\n   * @secure\n   */\n  v1ImportlistexclusionCreate = (data: ImportListExclusionResource, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v1/importlistexclusion`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V1ImportlistexclusionUpdate\n   * @request PUT:/api/v1/importlistexclusion/{id}\n   * @secure\n   */\n  v1ImportlistexclusionUpdate = (id: string, data: ImportListExclusionResource, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v1/importlistexclusion/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V1ImportlistexclusionDelete\n   * @request DELETE:/api/v1/importlistexclusion/{id}\n   * @secure\n   */\n  v1ImportlistexclusionDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/importlistexclusion/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V1ImportlistexclusionDetail\n   * @request GET:/api/v1/importlistexclusion/{id}\n   * @secure\n   */\n  v1ImportlistexclusionDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v1/importlistexclusion/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerList\n   * @request GET:/api/v1/indexer\n   * @secure\n   */\n  v1IndexerList = (params: RequestParams = {}) =>\n    this.http.request<IndexerResource[], any>({\n      path: `/api/v1/indexer`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerCreate\n   * @request POST:/api/v1/indexer\n   * @secure\n   */\n  v1IndexerCreate = (\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v1/indexer`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerUpdate\n   * @request PUT:/api/v1/indexer/{id}\n   * @secure\n   */\n  v1IndexerUpdate = (\n    id: string,\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v1/indexer/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerDelete\n   * @request DELETE:/api/v1/indexer/{id}\n   * @secure\n   */\n  v1IndexerDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/indexer/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerDetail\n   * @request GET:/api/v1/indexer/{id}\n   * @secure\n   */\n  v1IndexerDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v1/indexer/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerBulkUpdate\n   * @request PUT:/api/v1/indexer/bulk\n   * @secure\n   */\n  v1IndexerBulkUpdate = (data: IndexerBulkResource, params: RequestParams = {}) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v1/indexer/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerBulkDelete\n   * @request DELETE:/api/v1/indexer/bulk\n   * @secure\n   */\n  v1IndexerBulkDelete = (data: IndexerBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/indexer/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerSchemaList\n   * @request GET:/api/v1/indexer/schema\n   * @secure\n   */\n  v1IndexerSchemaList = (params: RequestParams = {}) =>\n    this.http.request<IndexerResource[], any>({\n      path: `/api/v1/indexer/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerTestCreate\n   * @request POST:/api/v1/indexer/test\n   * @secure\n   */\n  v1IndexerTestCreate = (\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/indexer/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerTestallCreate\n   * @request POST:/api/v1/indexer/testall\n   * @secure\n   */\n  v1IndexerTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/indexer/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V1IndexerActionCreate\n   * @request POST:/api/v1/indexer/action/{name}\n   * @secure\n   */\n  v1IndexerActionCreate = (name: string, data: IndexerResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/indexer/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V1ConfigIndexerList\n   * @request GET:/api/v1/config/indexer\n   * @secure\n   */\n  v1ConfigIndexerList = (params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v1/config/indexer`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V1ConfigIndexerUpdate\n   * @request PUT:/api/v1/config/indexer/{id}\n   * @secure\n   */\n  v1ConfigIndexerUpdate = (id: string, data: IndexerConfigResource, params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v1/config/indexer/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V1ConfigIndexerDetail\n   * @request GET:/api/v1/config/indexer/{id}\n   * @secure\n   */\n  v1ConfigIndexerDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v1/config/indexer/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerFlag\n   * @name V1IndexerflagList\n   * @request GET:/api/v1/indexerflag\n   * @secure\n   */\n  v1IndexerflagList = (params: RequestParams = {}) =>\n    this.http.request<IndexerFlagResource[], any>({\n      path: `/api/v1/indexerflag`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Language\n   * @name V1LanguageList\n   * @request GET:/api/v1/language\n   * @secure\n   */\n  v1LanguageList = (params: RequestParams = {}) =>\n    this.http.request<LanguageResource[], any>({\n      path: `/api/v1/language`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Language\n   * @name V1LanguageDetail\n   * @request GET:/api/v1/language/{id}\n   * @secure\n   */\n  v1LanguageDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<LanguageResource, any>({\n      path: `/api/v1/language/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Localization\n   * @name V1LocalizationList\n   * @request GET:/api/v1/localization\n   * @secure\n   */\n  v1LocalizationList = (params: RequestParams = {}) =>\n    this.http.request<string, any>({\n      path: `/api/v1/localization`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Log\n   * @name V1LogList\n   * @request GET:/api/v1/log\n   * @secure\n   */\n  v1LogList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      level?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<LogResourcePagingResource, any>({\n      path: `/api/v1/log`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LogFile\n   * @name V1LogFileList\n   * @request GET:/api/v1/log/file\n   * @secure\n   */\n  v1LogFileList = (params: RequestParams = {}) =>\n    this.http.request<LogFileResource[], any>({\n      path: `/api/v1/log/file`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LogFile\n   * @name V1LogFileDetail\n   * @request GET:/api/v1/log/file/{filename}\n   * @secure\n   */\n  v1LogFileDetail = (filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/log/file/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ManualImport\n   * @name V1ManualimportCreate\n   * @request POST:/api/v1/manualimport\n   * @secure\n   */\n  v1ManualimportCreate = (data: ManualImportUpdateResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/manualimport`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ManualImport\n   * @name V1ManualimportList\n   * @request GET:/api/v1/manualimport\n   * @secure\n   */\n  v1ManualimportList = (\n    query?: {\n      folder?: string;\n      downloadId?: string;\n      /** @format int32 */\n      authorId?: number;\n      /** @default true */\n      filterExistingFiles?: boolean;\n      /** @default true */\n      replaceExistingFiles?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ManualImportResource[], any>({\n      path: `/api/v1/manualimport`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaCover\n   * @name V1MediacoverAuthorDetail\n   * @request GET:/api/v1/mediacover/author/{authorId}/{filename}\n   * @secure\n   */\n  v1MediacoverAuthorDetail = (authorId: number, filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/mediacover/author/${authorId}/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaCover\n   * @name V1MediacoverBookDetail\n   * @request GET:/api/v1/mediacover/book/{bookId}/{filename}\n   * @secure\n   */\n  v1MediacoverBookDetail = (bookId: number, filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/mediacover/book/${bookId}/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V1ConfigMediamanagementList\n   * @request GET:/api/v1/config/mediamanagement\n   * @secure\n   */\n  v1ConfigMediamanagementList = (params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v1/config/mediamanagement`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V1ConfigMediamanagementUpdate\n   * @request PUT:/api/v1/config/mediamanagement/{id}\n   * @secure\n   */\n  v1ConfigMediamanagementUpdate = (id: string, data: MediaManagementConfigResource, params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v1/config/mediamanagement/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V1ConfigMediamanagementDetail\n   * @request GET:/api/v1/config/mediamanagement/{id}\n   * @secure\n   */\n  v1ConfigMediamanagementDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v1/config/mediamanagement/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataList\n   * @request GET:/api/v1/metadata\n   * @secure\n   */\n  v1MetadataList = (params: RequestParams = {}) =>\n    this.http.request<MetadataResource[], any>({\n      path: `/api/v1/metadata`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataCreate\n   * @request POST:/api/v1/metadata\n   * @secure\n   */\n  v1MetadataCreate = (\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v1/metadata`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataUpdate\n   * @request PUT:/api/v1/metadata/{id}\n   * @secure\n   */\n  v1MetadataUpdate = (\n    id: string,\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v1/metadata/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataDelete\n   * @request DELETE:/api/v1/metadata/{id}\n   * @secure\n   */\n  v1MetadataDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/metadata/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataDetail\n   * @request GET:/api/v1/metadata/{id}\n   * @secure\n   */\n  v1MetadataDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v1/metadata/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataSchemaList\n   * @request GET:/api/v1/metadata/schema\n   * @secure\n   */\n  v1MetadataSchemaList = (params: RequestParams = {}) =>\n    this.http.request<MetadataResource[], any>({\n      path: `/api/v1/metadata/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataTestCreate\n   * @request POST:/api/v1/metadata/test\n   * @secure\n   */\n  v1MetadataTestCreate = (\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/metadata/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataTestallCreate\n   * @request POST:/api/v1/metadata/testall\n   * @secure\n   */\n  v1MetadataTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/metadata/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V1MetadataActionCreate\n   * @request POST:/api/v1/metadata/action/{name}\n   * @secure\n   */\n  v1MetadataActionCreate = (name: string, data: MetadataResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/metadata/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProfile\n   * @name V1MetadataprofileCreate\n   * @request POST:/api/v1/metadataprofile\n   * @secure\n   */\n  v1MetadataprofileCreate = (data: MetadataProfileResource, params: RequestParams = {}) =>\n    this.http.request<MetadataProfileResource, any>({\n      path: `/api/v1/metadataprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProfile\n   * @name V1MetadataprofileList\n   * @request GET:/api/v1/metadataprofile\n   * @secure\n   */\n  v1MetadataprofileList = (params: RequestParams = {}) =>\n    this.http.request<MetadataProfileResource[], any>({\n      path: `/api/v1/metadataprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProfile\n   * @name V1MetadataprofileDelete\n   * @request DELETE:/api/v1/metadataprofile/{id}\n   * @secure\n   */\n  v1MetadataprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/metadataprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProfile\n   * @name V1MetadataprofileUpdate\n   * @request PUT:/api/v1/metadataprofile/{id}\n   * @secure\n   */\n  v1MetadataprofileUpdate = (id: string, data: MetadataProfileResource, params: RequestParams = {}) =>\n    this.http.request<MetadataProfileResource, any>({\n      path: `/api/v1/metadataprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProfile\n   * @name V1MetadataprofileDetail\n   * @request GET:/api/v1/metadataprofile/{id}\n   * @secure\n   */\n  v1MetadataprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MetadataProfileResource, any>({\n      path: `/api/v1/metadataprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProfileSchema\n   * @name V1MetadataprofileSchemaList\n   * @request GET:/api/v1/metadataprofile/schema\n   * @secure\n   */\n  v1MetadataprofileSchemaList = (params: RequestParams = {}) =>\n    this.http.request<MetadataProfileResource, any>({\n      path: `/api/v1/metadataprofile/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProviderConfig\n   * @name V1ConfigMetadataproviderList\n   * @request GET:/api/v1/config/metadataprovider\n   * @secure\n   */\n  v1ConfigMetadataproviderList = (params: RequestParams = {}) =>\n    this.http.request<MetadataProviderConfigResource, any>({\n      path: `/api/v1/config/metadataprovider`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProviderConfig\n   * @name V1ConfigMetadataproviderUpdate\n   * @request PUT:/api/v1/config/metadataprovider/{id}\n   * @secure\n   */\n  v1ConfigMetadataproviderUpdate = (id: string, data: MetadataProviderConfigResource, params: RequestParams = {}) =>\n    this.http.request<MetadataProviderConfigResource, any>({\n      path: `/api/v1/config/metadataprovider/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MetadataProviderConfig\n   * @name V1ConfigMetadataproviderDetail\n   * @request GET:/api/v1/config/metadataprovider/{id}\n   * @secure\n   */\n  v1ConfigMetadataproviderDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MetadataProviderConfigResource, any>({\n      path: `/api/v1/config/metadataprovider/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Missing\n   * @name V1WantedMissingList\n   * @request GET:/api/v1/wanted/missing\n   * @secure\n   */\n  v1WantedMissingList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeAuthor?: boolean;\n      /** @default true */\n      monitored?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<BookResourcePagingResource, any>({\n      path: `/api/v1/wanted/missing`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Missing\n   * @name V1WantedMissingDetail\n   * @request GET:/api/v1/wanted/missing/{id}\n   * @secure\n   */\n  v1WantedMissingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<BookResource, any>({\n      path: `/api/v1/wanted/missing/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V1ConfigNamingList\n   * @request GET:/api/v1/config/naming\n   * @secure\n   */\n  v1ConfigNamingList = (params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v1/config/naming`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V1ConfigNamingUpdate\n   * @request PUT:/api/v1/config/naming/{id}\n   * @secure\n   */\n  v1ConfigNamingUpdate = (id: string, data: NamingConfigResource, params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v1/config/naming/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V1ConfigNamingDetail\n   * @request GET:/api/v1/config/naming/{id}\n   * @secure\n   */\n  v1ConfigNamingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v1/config/naming/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V1ConfigNamingExamplesList\n   * @request GET:/api/v1/config/naming/examples\n   * @secure\n   */\n  v1ConfigNamingExamplesList = (\n    query?: {\n      renameBooks?: boolean;\n      replaceIllegalCharacters?: boolean;\n      /** @format int32 */\n      colonReplacementFormat?: number;\n      standardBookFormat?: string;\n      authorFolderFormat?: string;\n      includeAuthorName?: boolean;\n      includeBookTitle?: boolean;\n      includeQuality?: boolean;\n      replaceSpaces?: boolean;\n      separator?: string;\n      numberStyle?: string;\n      /** @format int32 */\n      id?: number;\n      resourceName?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/config/naming/examples`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationList\n   * @request GET:/api/v1/notification\n   * @secure\n   */\n  v1NotificationList = (params: RequestParams = {}) =>\n    this.http.request<NotificationResource[], any>({\n      path: `/api/v1/notification`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationCreate\n   * @request POST:/api/v1/notification\n   * @secure\n   */\n  v1NotificationCreate = (\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v1/notification`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationUpdate\n   * @request PUT:/api/v1/notification/{id}\n   * @secure\n   */\n  v1NotificationUpdate = (\n    id: string,\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v1/notification/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationDelete\n   * @request DELETE:/api/v1/notification/{id}\n   * @secure\n   */\n  v1NotificationDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/notification/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationDetail\n   * @request GET:/api/v1/notification/{id}\n   * @secure\n   */\n  v1NotificationDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v1/notification/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationSchemaList\n   * @request GET:/api/v1/notification/schema\n   * @secure\n   */\n  v1NotificationSchemaList = (params: RequestParams = {}) =>\n    this.http.request<NotificationResource[], any>({\n      path: `/api/v1/notification/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationTestCreate\n   * @request POST:/api/v1/notification/test\n   * @secure\n   */\n  v1NotificationTestCreate = (\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/notification/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationTestallCreate\n   * @request POST:/api/v1/notification/testall\n   * @secure\n   */\n  v1NotificationTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/notification/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V1NotificationActionCreate\n   * @request POST:/api/v1/notification/action/{name}\n   * @secure\n   */\n  v1NotificationActionCreate = (name: string, data: NotificationResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/notification/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Parse\n   * @name V1ParseList\n   * @request GET:/api/v1/parse\n   * @secure\n   */\n  v1ParseList = (\n    query?: {\n      title?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ParseResource, any>({\n      path: `/api/v1/parse`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V1QualitydefinitionUpdate\n   * @request PUT:/api/v1/qualitydefinition/{id}\n   * @secure\n   */\n  v1QualitydefinitionUpdate = (id: string, data: QualityDefinitionResource, params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource, any>({\n      path: `/api/v1/qualitydefinition/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V1QualitydefinitionDetail\n   * @request GET:/api/v1/qualitydefinition/{id}\n   * @secure\n   */\n  v1QualitydefinitionDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource, any>({\n      path: `/api/v1/qualitydefinition/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V1QualitydefinitionList\n   * @request GET:/api/v1/qualitydefinition\n   * @secure\n   */\n  v1QualitydefinitionList = (params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource[], any>({\n      path: `/api/v1/qualitydefinition`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V1QualitydefinitionUpdateUpdate\n   * @request PUT:/api/v1/qualitydefinition/update\n   * @secure\n   */\n  v1QualitydefinitionUpdateUpdate = (data: QualityDefinitionResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/qualitydefinition/update`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V1QualityprofileCreate\n   * @request POST:/api/v1/qualityprofile\n   * @secure\n   */\n  v1QualityprofileCreate = (data: QualityProfileResource, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v1/qualityprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V1QualityprofileList\n   * @request GET:/api/v1/qualityprofile\n   * @secure\n   */\n  v1QualityprofileList = (params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource[], any>({\n      path: `/api/v1/qualityprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V1QualityprofileDelete\n   * @request DELETE:/api/v1/qualityprofile/{id}\n   * @secure\n   */\n  v1QualityprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/qualityprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V1QualityprofileUpdate\n   * @request PUT:/api/v1/qualityprofile/{id}\n   * @secure\n   */\n  v1QualityprofileUpdate = (id: string, data: QualityProfileResource, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v1/qualityprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V1QualityprofileDetail\n   * @request GET:/api/v1/qualityprofile/{id}\n   * @secure\n   */\n  v1QualityprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v1/qualityprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfileSchema\n   * @name V1QualityprofileSchemaList\n   * @request GET:/api/v1/qualityprofile/schema\n   * @secure\n   */\n  v1QualityprofileSchemaList = (params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v1/qualityprofile/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V1QueueDelete\n   * @request DELETE:/api/v1/queue/{id}\n   * @secure\n   */\n  v1QueueDelete = (\n    id: number,\n    query?: {\n      /** @default true */\n      removeFromClient?: boolean;\n      /** @default false */\n      blocklist?: boolean;\n      /** @default false */\n      skipRedownload?: boolean;\n      /** @default false */\n      changeCategory?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/queue/${id}`,\n      method: \"DELETE\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V1QueueBulkDelete\n   * @request DELETE:/api/v1/queue/bulk\n   * @secure\n   */\n  v1QueueBulkDelete = (\n    data: QueueBulkResource,\n    query?: {\n      /** @default true */\n      removeFromClient?: boolean;\n      /** @default false */\n      blocklist?: boolean;\n      /** @default false */\n      skipRedownload?: boolean;\n      /** @default false */\n      changeCategory?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/queue/bulk`,\n      method: \"DELETE\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V1QueueList\n   * @request GET:/api/v1/queue\n   * @secure\n   */\n  v1QueueList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeUnknownAuthorItems?: boolean;\n      /** @default false */\n      includeAuthor?: boolean;\n      /** @default false */\n      includeBook?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<QueueResourcePagingResource, any>({\n      path: `/api/v1/queue`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueAction\n   * @name V1QueueGrabCreate\n   * @request POST:/api/v1/queue/grab/{id}\n   * @secure\n   */\n  v1QueueGrabCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/queue/grab/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueAction\n   * @name V1QueueGrabBulkCreate\n   * @request POST:/api/v1/queue/grab/bulk\n   * @secure\n   */\n  v1QueueGrabBulkCreate = (data: QueueBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/queue/grab/bulk`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueDetails\n   * @name V1QueueDetailsList\n   * @request GET:/api/v1/queue/details\n   * @secure\n   */\n  v1QueueDetailsList = (\n    query?: {\n      /** @format int32 */\n      authorId?: number;\n      bookIds?: number[];\n      /** @default false */\n      includeAuthor?: boolean;\n      /** @default true */\n      includeBook?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<QueueResource[], any>({\n      path: `/api/v1/queue/details`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueStatus\n   * @name V1QueueStatusList\n   * @request GET:/api/v1/queue/status\n   * @secure\n   */\n  v1QueueStatusList = (params: RequestParams = {}) =>\n    this.http.request<QueueStatusResource, any>({\n      path: `/api/v1/queue/status`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Release\n   * @name V1ReleaseCreate\n   * @request POST:/api/v1/release\n   * @secure\n   */\n  v1ReleaseCreate = (data: ReleaseResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseResource, any>({\n      path: `/api/v1/release`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Release\n   * @name V1ReleaseList\n   * @request GET:/api/v1/release\n   * @secure\n   */\n  v1ReleaseList = (\n    query?: {\n      /** @format int32 */\n      bookId?: number;\n      /** @format int32 */\n      authorId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ReleaseResource[], any>({\n      path: `/api/v1/release`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V1ReleaseprofileList\n   * @request GET:/api/v1/releaseprofile\n   * @secure\n   */\n  v1ReleaseprofileList = (params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource[], any>({\n      path: `/api/v1/releaseprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V1ReleaseprofileCreate\n   * @request POST:/api/v1/releaseprofile\n   * @secure\n   */\n  v1ReleaseprofileCreate = (data: ReleaseProfileResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v1/releaseprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V1ReleaseprofileUpdate\n   * @request PUT:/api/v1/releaseprofile/{id}\n   * @secure\n   */\n  v1ReleaseprofileUpdate = (id: string, data: ReleaseProfileResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v1/releaseprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V1ReleaseprofileDelete\n   * @request DELETE:/api/v1/releaseprofile/{id}\n   * @secure\n   */\n  v1ReleaseprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/releaseprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V1ReleaseprofileDetail\n   * @request GET:/api/v1/releaseprofile/{id}\n   * @secure\n   */\n  v1ReleaseprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v1/releaseprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleasePush\n   * @name V1ReleasePushCreate\n   * @request POST:/api/v1/release/push\n   * @secure\n   */\n  v1ReleasePushCreate = (data: ReleaseResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseResource, any>({\n      path: `/api/v1/release/push`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V1RemotepathmappingCreate\n   * @request POST:/api/v1/remotepathmapping\n   * @secure\n   */\n  v1RemotepathmappingCreate = (data: RemotePathMappingResource, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v1/remotepathmapping`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V1RemotepathmappingList\n   * @request GET:/api/v1/remotepathmapping\n   * @secure\n   */\n  v1RemotepathmappingList = (params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource[], any>({\n      path: `/api/v1/remotepathmapping`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V1RemotepathmappingDelete\n   * @request DELETE:/api/v1/remotepathmapping/{id}\n   * @secure\n   */\n  v1RemotepathmappingDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/remotepathmapping/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V1RemotepathmappingUpdate\n   * @request PUT:/api/v1/remotepathmapping/{id}\n   * @secure\n   */\n  v1RemotepathmappingUpdate = (id: string, data: RemotePathMappingResource, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v1/remotepathmapping/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V1RemotepathmappingDetail\n   * @request GET:/api/v1/remotepathmapping/{id}\n   * @secure\n   */\n  v1RemotepathmappingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v1/remotepathmapping/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RenameBook\n   * @name V1RenameList\n   * @request GET:/api/v1/rename\n   * @secure\n   */\n  v1RenameList = (\n    query?: {\n      /** @format int32 */\n      authorId?: number;\n      /** @format int32 */\n      bookId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<RenameBookResource[], any>({\n      path: `/api/v1/rename`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RetagBook\n   * @name V1RetagList\n   * @request GET:/api/v1/retag\n   * @secure\n   */\n  v1RetagList = (\n    query?: {\n      /** @format int32 */\n      authorId?: number;\n      /** @format int32 */\n      bookId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<RetagBookResource[], any>({\n      path: `/api/v1/retag`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V1RootfolderCreate\n   * @request POST:/api/v1/rootfolder\n   * @secure\n   */\n  v1RootfolderCreate = (data: RootFolderResource, params: RequestParams = {}) =>\n    this.http.request<RootFolderResource, any>({\n      path: `/api/v1/rootfolder`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V1RootfolderList\n   * @request GET:/api/v1/rootfolder\n   * @secure\n   */\n  v1RootfolderList = (params: RequestParams = {}) =>\n    this.http.request<RootFolderResource[], any>({\n      path: `/api/v1/rootfolder`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V1RootfolderUpdate\n   * @request PUT:/api/v1/rootfolder/{id}\n   * @secure\n   */\n  v1RootfolderUpdate = (id: string, data: RootFolderResource, params: RequestParams = {}) =>\n    this.http.request<RootFolderResource, any>({\n      path: `/api/v1/rootfolder/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V1RootfolderDelete\n   * @request DELETE:/api/v1/rootfolder/{id}\n   * @secure\n   */\n  v1RootfolderDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/rootfolder/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V1RootfolderDetail\n   * @request GET:/api/v1/rootfolder/{id}\n   * @secure\n   */\n  v1RootfolderDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<RootFolderResource, any>({\n      path: `/api/v1/rootfolder/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Search\n   * @name V1SearchList\n   * @request GET:/api/v1/search\n   * @secure\n   */\n  v1SearchList = (\n    query?: {\n      term?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v1/search`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Series\n   * @name V1SeriesList\n   * @request GET:/api/v1/series\n   * @secure\n   */\n  v1SeriesList = (\n    query?: {\n      /** @format int32 */\n      authorId?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<SeriesResource[], any>({\n      path: `/api/v1/series`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V1SystemStatusList\n   * @request GET:/api/v1/system/status\n   * @secure\n   */\n  v1SystemStatusList = (params: RequestParams = {}) =>\n    this.http.request<SystemResource, any>({\n      path: `/api/v1/system/status`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V1SystemRoutesList\n   * @request GET:/api/v1/system/routes\n   * @secure\n   */\n  v1SystemRoutesList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/routes`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V1SystemRoutesDuplicateList\n   * @request GET:/api/v1/system/routes/duplicate\n   * @secure\n   */\n  v1SystemRoutesDuplicateList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/routes/duplicate`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V1SystemShutdownCreate\n   * @request POST:/api/v1/system/shutdown\n   * @secure\n   */\n  v1SystemShutdownCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/shutdown`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V1SystemRestartCreate\n   * @request POST:/api/v1/system/restart\n   * @secure\n   */\n  v1SystemRestartCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/system/restart`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V1TagList\n   * @request GET:/api/v1/tag\n   * @secure\n   */\n  v1TagList = (params: RequestParams = {}) =>\n    this.http.request<TagResource[], any>({\n      path: `/api/v1/tag`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V1TagCreate\n   * @request POST:/api/v1/tag\n   * @secure\n   */\n  v1TagCreate = (data: TagResource, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v1/tag`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V1TagUpdate\n   * @request PUT:/api/v1/tag/{id}\n   * @secure\n   */\n  v1TagUpdate = (id: string, data: TagResource, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v1/tag/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V1TagDelete\n   * @request DELETE:/api/v1/tag/{id}\n   * @secure\n   */\n  v1TagDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/tag/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V1TagDetail\n   * @request GET:/api/v1/tag/{id}\n   * @secure\n   */\n  v1TagDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v1/tag/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TagDetails\n   * @name V1TagDetailList\n   * @request GET:/api/v1/tag/detail\n   * @secure\n   */\n  v1TagDetailList = (params: RequestParams = {}) =>\n    this.http.request<TagDetailsResource[], any>({\n      path: `/api/v1/tag/detail`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TagDetails\n   * @name V1TagDetailDetail\n   * @request GET:/api/v1/tag/detail/{id}\n   * @secure\n   */\n  v1TagDetailDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TagDetailsResource, any>({\n      path: `/api/v1/tag/detail/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Task\n   * @name V1SystemTaskList\n   * @request GET:/api/v1/system/task\n   * @secure\n   */\n  v1SystemTaskList = (params: RequestParams = {}) =>\n    this.http.request<TaskResource[], any>({\n      path: `/api/v1/system/task`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Task\n   * @name V1SystemTaskDetail\n   * @request GET:/api/v1/system/task/{id}\n   * @secure\n   */\n  v1SystemTaskDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TaskResource, any>({\n      path: `/api/v1/system/task/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V1ConfigUiUpdate\n   * @request PUT:/api/v1/config/ui/{id}\n   * @secure\n   */\n  v1ConfigUiUpdate = (id: string, data: UiConfigResource, params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v1/config/ui/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V1ConfigUiDetail\n   * @request GET:/api/v1/config/ui/{id}\n   * @secure\n   */\n  v1ConfigUiDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v1/config/ui/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V1ConfigUiList\n   * @request GET:/api/v1/config/ui\n   * @secure\n   */\n  v1ConfigUiList = (params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v1/config/ui`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Update\n   * @name V1UpdateList\n   * @request GET:/api/v1/update\n   * @secure\n   */\n  v1UpdateList = (params: RequestParams = {}) =>\n    this.http.request<UpdateResource[], any>({\n      path: `/api/v1/update`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UpdateLogFile\n   * @name V1LogFileUpdateList\n   * @request GET:/api/v1/log/file/update\n   * @secure\n   */\n  v1LogFileUpdateList = (params: RequestParams = {}) =>\n    this.http.request<LogFileResource[], any>({\n      path: `/api/v1/log/file/update`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UpdateLogFile\n   * @name V1LogFileUpdateDetail\n   * @request GET:/api/v1/log/file/update/{filename}\n   * @secure\n   */\n  v1LogFileUpdateDetail = (filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v1/log/file/update/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/readarr/Content.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Content<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name ContentDetail\n   * @request GET:/content/{path}\n   * @secure\n   */\n  contentDetail = (path: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/content/${path}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/readarr/Feed.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Feed<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags CalendarFeed\n   * @name V1CalendarReadarrIcsList\n   * @request GET:/feed/v1/calendar/readarr.ics\n   * @secure\n   */\n  v1CalendarReadarrIcsList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 7\n       */\n      pastDays?: number;\n      /**\n       * @format int32\n       * @default 28\n       */\n      futureDays?: number;\n      /** @default \"\" */\n      tagList?: string;\n      /** @default false */\n      unmonitored?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/feed/v1/calendar/readarr.ics`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/readarr/Login.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { ContentType, HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Login<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Authentication\n   * @name LoginCreate\n   * @request POST:/login\n   * @secure\n   */\n  loginCreate = (\n    data: {\n      username?: string;\n      password?: string;\n      rememberMe?: string;\n    },\n    query?: {\n      returnUrl?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/login`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.FormData,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name LoginList\n   * @request GET:/login\n   * @secure\n   */\n  loginList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/login`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/readarr/Logout.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Logout<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Authentication\n   * @name LogoutList\n   * @request GET:/logout\n   * @secure\n   */\n  logoutList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/logout`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/readarr/Path.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Path<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name GetPath\n   * @request GET:/{path}\n   * @secure\n   */\n  getPath = (path: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/${path}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/readarr/Ping.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\nimport { PingResource } from \"./data-contracts\";\n\nexport class Ping<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Ping\n   * @name PingList\n   * @request GET:/ping\n   * @secure\n   */\n  pingList = (params: RequestParams = {}) =>\n    this.http.request<PingResource, any>({\n      path: `/ping`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Ping\n   * @name HeadPing\n   * @request HEAD:/ping\n   * @secure\n   */\n  headPing = (params: RequestParams = {}) =>\n    this.http.request<PingResource, any>({\n      path: `/ping`,\n      method: \"HEAD\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/readarr/data-contracts.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nexport enum WriteBookTagsType {\n  NewFiles = \"newFiles\",\n  AllFiles = \"allFiles\",\n  Sync = \"sync\",\n}\n\nexport enum WriteAudioTagsType {\n  No = \"no\",\n  NewFiles = \"newFiles\",\n  AllFiles = \"allFiles\",\n  Sync = \"sync\",\n}\n\nexport enum UpdateMechanism {\n  BuiltIn = \"builtIn\",\n  Script = \"script\",\n  External = \"external\",\n  Apt = \"apt\",\n  Docker = \"docker\",\n}\n\nexport enum TrackedDownloadStatus {\n  Ok = \"ok\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum TrackedDownloadState {\n  Downloading = \"downloading\",\n  DownloadFailed = \"downloadFailed\",\n  DownloadFailedPending = \"downloadFailedPending\",\n  ImportPending = \"importPending\",\n  Importing = \"importing\",\n  ImportFailed = \"importFailed\",\n  Imported = \"imported\",\n  Ignored = \"ignored\",\n}\n\nexport enum SortDirection {\n  Default = \"default\",\n  Ascending = \"ascending\",\n  Descending = \"descending\",\n}\n\nexport enum RuntimeMode {\n  Console = \"console\",\n  Service = \"service\",\n  Tray = \"tray\",\n}\n\nexport enum RescanAfterRefreshType {\n  Always = \"always\",\n  AfterManual = \"afterManual\",\n  Never = \"never\",\n}\n\nexport enum RejectionType {\n  Permanent = \"permanent\",\n  Temporary = \"temporary\",\n}\n\nexport enum ProxyType {\n  Http = \"http\",\n  Socks4 = \"socks4\",\n  Socks5 = \"socks5\",\n}\n\nexport enum ProviderMessageType {\n  Info = \"info\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum ProperDownloadTypes {\n  PreferAndUpgrade = \"preferAndUpgrade\",\n  DoNotUpgrade = \"doNotUpgrade\",\n  DoNotPrefer = \"doNotPrefer\",\n}\n\nexport enum NewItemMonitorTypes {\n  All = \"all\",\n  None = \"none\",\n  New = \"new\",\n}\n\nexport enum MonitorTypes {\n  All = \"all\",\n  Future = \"future\",\n  Missing = \"missing\",\n  Existing = \"existing\",\n  Latest = \"latest\",\n  First = \"first\",\n  None = \"none\",\n  Unknown = \"unknown\",\n}\n\nexport enum MediaCoverTypes {\n  Unknown = \"unknown\",\n  Poster = \"poster\",\n  Banner = \"banner\",\n  Fanart = \"fanart\",\n  Screenshot = \"screenshot\",\n  Headshot = \"headshot\",\n  Cover = \"cover\",\n  Disc = \"disc\",\n  Logo = \"logo\",\n  Clearlogo = \"clearlogo\",\n}\n\nexport enum IndexerFlags {\n  Freeleech = \"freeleech\",\n  Halfleech = \"halfleech\",\n  DoubleUpload = \"doubleUpload\",\n  Internal = \"internal\",\n  Scene = \"scene\",\n  Freeleech75 = \"freeleech75\",\n  Freeleech25 = \"freeleech25\",\n}\n\nexport enum ImportListType {\n  Program = \"program\",\n  Goodreads = \"goodreads\",\n  Other = \"other\",\n}\n\nexport enum ImportListMonitorType {\n  None = \"none\",\n  SpecificBook = \"specificBook\",\n  EntireAuthor = \"entireAuthor\",\n}\n\nexport enum HealthCheckResult {\n  Ok = \"ok\",\n  Notice = \"notice\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum FileDateType {\n  None = \"none\",\n  BookReleaseDate = \"bookReleaseDate\",\n}\n\nexport enum EntityHistoryEventType {\n  Unknown = \"unknown\",\n  Grabbed = \"grabbed\",\n  BookFileImported = \"bookFileImported\",\n  DownloadFailed = \"downloadFailed\",\n  BookFileDeleted = \"bookFileDeleted\",\n  BookFileRenamed = \"bookFileRenamed\",\n  BookImportIncomplete = \"bookImportIncomplete\",\n  DownloadImported = \"downloadImported\",\n  BookFileRetagged = \"bookFileRetagged\",\n  DownloadIgnored = \"downloadIgnored\",\n}\n\nexport enum DownloadProtocol {\n  Unknown = \"unknown\",\n  Usenet = \"usenet\",\n  Torrent = \"torrent\",\n}\n\nexport enum DatabaseType {\n  SqLite = \"sqLite\",\n  PostgreSQL = \"postgreSQL\",\n}\n\nexport enum CommandTrigger {\n  Unspecified = \"unspecified\",\n  Manual = \"manual\",\n  Scheduled = \"scheduled\",\n}\n\nexport enum CommandStatus {\n  Queued = \"queued\",\n  Started = \"started\",\n  Completed = \"completed\",\n  Failed = \"failed\",\n  Aborted = \"aborted\",\n  Cancelled = \"cancelled\",\n  Orphaned = \"orphaned\",\n}\n\nexport enum CommandResult {\n  Unknown = \"unknown\",\n  Successful = \"successful\",\n  Unsuccessful = \"unsuccessful\",\n}\n\nexport enum CommandPriority {\n  Normal = \"normal\",\n  High = \"high\",\n  Low = \"low\",\n}\n\nexport enum CertificateValidationType {\n  Enabled = \"enabled\",\n  DisabledForLocalAddresses = \"disabledForLocalAddresses\",\n  Disabled = \"disabled\",\n}\n\nexport enum BookAddType {\n  Automatic = \"automatic\",\n  Manual = \"manual\",\n}\n\nexport enum BackupType {\n  Scheduled = \"scheduled\",\n  Manual = \"manual\",\n  Update = \"update\",\n}\n\nexport enum AuthorStatusType {\n  Continuing = \"continuing\",\n  Ended = \"ended\",\n}\n\nexport enum AuthenticationType {\n  None = \"none\",\n  Basic = \"basic\",\n  Forms = \"forms\",\n  External = \"external\",\n}\n\nexport enum AuthenticationRequiredType {\n  Enabled = \"enabled\",\n  DisabledForLocalAddresses = \"disabledForLocalAddresses\",\n}\n\nexport enum ApplyTags {\n  Add = \"add\",\n  Remove = \"remove\",\n  Replace = \"replace\",\n}\n\nexport enum AllowFingerprinting {\n  Never = \"never\",\n  NewFiles = \"newFiles\",\n  AllFiles = \"allFiles\",\n}\n\nexport interface AddAuthorOptions {\n  monitor?: MonitorTypes;\n  booksToMonitor?: string[] | null;\n  monitored?: boolean;\n  searchForMissingBooks?: boolean;\n}\n\nexport interface AddBookOptions {\n  addType?: BookAddType;\n  searchForNewBook?: boolean;\n}\n\nexport interface ApiInfoResource {\n  current?: string | null;\n  deprecated?: string[] | null;\n}\n\nexport interface Author {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  authorMetadataId?: number;\n  cleanName?: string | null;\n  monitored?: boolean;\n  monitorNewItems?: NewItemMonitorTypes;\n  /** @format date-time */\n  lastInfoSync?: string | null;\n  path?: string | null;\n  rootFolderPath?: string | null;\n  /** @format date-time */\n  added?: string;\n  /** @format int32 */\n  qualityProfileId?: number;\n  /** @format int32 */\n  metadataProfileId?: number;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  addOptions?: AddAuthorOptions;\n  metadata?: AuthorMetadataLazyLoaded;\n  qualityProfile?: QualityProfileLazyLoaded;\n  metadataProfile?: MetadataProfileLazyLoaded;\n  books?: BookListLazyLoaded;\n  series?: SeriesListLazyLoaded;\n  name?: string | null;\n  foreignAuthorId?: string | null;\n}\n\nexport interface AuthorEditorResource {\n  authorIds?: number[] | null;\n  monitored?: boolean | null;\n  monitorNewItems?: NewItemMonitorTypes;\n  /** @format int32 */\n  qualityProfileId?: number | null;\n  /** @format int32 */\n  metadataProfileId?: number | null;\n  rootFolderPath?: string | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  moveFiles?: boolean;\n  deleteFiles?: boolean;\n}\n\nexport interface AuthorLazyLoaded {\n  value?: Author;\n  isLoaded?: boolean;\n}\n\nexport interface AuthorMetadata {\n  /** @format int32 */\n  id?: number;\n  foreignAuthorId?: string | null;\n  titleSlug?: string | null;\n  name?: string | null;\n  sortName?: string | null;\n  nameLastFirst?: string | null;\n  sortNameLastFirst?: string | null;\n  aliases?: string[] | null;\n  overview?: string | null;\n  disambiguation?: string | null;\n  gender?: string | null;\n  hometown?: string | null;\n  /** @format date-time */\n  born?: string | null;\n  /** @format date-time */\n  died?: string | null;\n  status?: AuthorStatusType;\n  images?: MediaCover[] | null;\n  links?: Links[] | null;\n  genres?: string[] | null;\n  ratings?: Ratings;\n}\n\nexport interface AuthorMetadataLazyLoaded {\n  value?: AuthorMetadata;\n  isLoaded?: boolean;\n}\n\nexport interface AuthorResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  authorMetadataId?: number;\n  status?: AuthorStatusType;\n  ended?: boolean;\n  authorName?: string | null;\n  authorNameLastFirst?: string | null;\n  foreignAuthorId?: string | null;\n  titleSlug?: string | null;\n  overview?: string | null;\n  disambiguation?: string | null;\n  links?: Links[] | null;\n  nextBook?: Book;\n  lastBook?: Book;\n  images?: MediaCover[] | null;\n  remotePoster?: string | null;\n  path?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number;\n  /** @format int32 */\n  metadataProfileId?: number;\n  monitored?: boolean;\n  monitorNewItems?: NewItemMonitorTypes;\n  rootFolderPath?: string | null;\n  folder?: string | null;\n  genres?: string[] | null;\n  cleanName?: string | null;\n  sortName?: string | null;\n  sortNameLastFirst?: string | null;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  /** @format date-time */\n  added?: string;\n  addOptions?: AddAuthorOptions;\n  ratings?: Ratings;\n  statistics?: AuthorStatisticsResource;\n}\n\nexport interface AuthorStatisticsResource {\n  /** @format int32 */\n  bookFileCount?: number;\n  /** @format int32 */\n  bookCount?: number;\n  /** @format int32 */\n  availableBookCount?: number;\n  /** @format int32 */\n  totalBookCount?: number;\n  /** @format int64 */\n  sizeOnDisk?: number;\n  /** @format double */\n  percentOfBooks?: number;\n}\n\nexport interface AuthorTitleInfo {\n  title?: string | null;\n  titleWithoutYear?: string | null;\n  /** @format int32 */\n  year?: number;\n}\n\nexport interface BackupResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  path?: string | null;\n  type?: BackupType;\n  /** @format int64 */\n  size?: number;\n  /** @format date-time */\n  time?: string;\n}\n\nexport interface BlocklistBulkResource {\n  ids?: number[] | null;\n}\n\nexport interface BlocklistResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  authorId?: number;\n  bookIds?: number[] | null;\n  sourceTitle?: string | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format date-time */\n  date?: string;\n  protocol?: DownloadProtocol;\n  indexer?: string | null;\n  message?: string | null;\n  author?: AuthorResource;\n}\n\nexport interface BlocklistResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: BlocklistResource[] | null;\n}\n\nexport interface Book {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  authorMetadataId?: number;\n  foreignBookId?: string | null;\n  foreignEditionId?: string | null;\n  titleSlug?: string | null;\n  title?: string | null;\n  /** @format date-time */\n  releaseDate?: string | null;\n  links?: Links[] | null;\n  genres?: string[] | null;\n  relatedBooks?: number[] | null;\n  ratings?: Ratings;\n  /** @format date-time */\n  lastSearchTime?: string | null;\n  cleanTitle?: string | null;\n  monitored?: boolean;\n  anyEditionOk?: boolean;\n  /** @format date-time */\n  lastInfoSync?: string | null;\n  /** @format date-time */\n  added?: string;\n  addOptions?: AddBookOptions;\n  authorMetadata?: AuthorMetadataLazyLoaded;\n  author?: AuthorLazyLoaded;\n  editions?: EditionListLazyLoaded;\n  bookFiles?: BookFileListLazyLoaded;\n  seriesLinks?: SeriesBookLinkListLazyLoaded;\n}\n\nexport interface BookEditorResource {\n  bookIds?: number[] | null;\n  monitored?: boolean | null;\n  deleteFiles?: boolean | null;\n  addImportListExclusion?: boolean | null;\n}\n\nexport interface BookFile {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  /** @format int64 */\n  size?: number;\n  /** @format date-time */\n  modified?: string;\n  /** @format date-time */\n  dateAdded?: string;\n  originalFilePath?: string | null;\n  sceneName?: string | null;\n  releaseGroup?: string | null;\n  quality?: QualityModel;\n  indexerFlags?: IndexerFlags;\n  mediaInfo?: MediaInfoModel;\n  /** @format int32 */\n  editionId?: number;\n  /** @format int32 */\n  calibreId?: number;\n  /** @format int32 */\n  part?: number;\n  author?: AuthorLazyLoaded;\n  edition?: EditionLazyLoaded;\n  /** @format int32 */\n  partCount?: number;\n}\n\nexport interface BookFileListLazyLoaded {\n  value?: BookFile[] | null;\n  isLoaded?: boolean;\n}\n\nexport interface BookFileListResource {\n  bookFileIds?: number[] | null;\n  quality?: QualityModel;\n}\n\nexport interface BookFileResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  authorId?: number;\n  /** @format int32 */\n  bookId?: number;\n  path?: string | null;\n  /** @format int64 */\n  size?: number;\n  /** @format date-time */\n  dateAdded?: string;\n  quality?: QualityModel;\n  /** @format int32 */\n  qualityWeight?: number;\n  /** @format int32 */\n  indexerFlags?: number | null;\n  mediaInfo?: MediaInfoResource;\n  qualityCutoffNotMet?: boolean;\n  audioTags?: ParsedTrackInfo;\n}\n\nexport interface BookLazyLoaded {\n  value?: Book;\n  isLoaded?: boolean;\n}\n\nexport interface BookListLazyLoaded {\n  value?: Book[] | null;\n  isLoaded?: boolean;\n}\n\nexport interface BookResource {\n  /** @format int32 */\n  id?: number;\n  title?: string | null;\n  authorTitle?: string | null;\n  seriesTitle?: string | null;\n  disambiguation?: string | null;\n  overview?: string | null;\n  /** @format int32 */\n  authorId?: number;\n  foreignBookId?: string | null;\n  foreignEditionId?: string | null;\n  titleSlug?: string | null;\n  monitored?: boolean;\n  anyEditionOk?: boolean;\n  ratings?: Ratings;\n  /** @format date-time */\n  releaseDate?: string | null;\n  /** @format int32 */\n  pageCount?: number;\n  genres?: string[] | null;\n  author?: AuthorResource;\n  images?: MediaCover[] | null;\n  links?: Links[] | null;\n  statistics?: BookStatisticsResource;\n  /** @format date-time */\n  added?: string | null;\n  addOptions?: AddBookOptions;\n  remoteCover?: string | null;\n  /** @format date-time */\n  lastSearchTime?: string | null;\n  editions?: EditionResource[] | null;\n}\n\nexport interface BookResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: BookResource[] | null;\n}\n\nexport interface BookStatisticsResource {\n  /** @format int32 */\n  bookFileCount?: number;\n  /** @format int32 */\n  bookCount?: number;\n  /** @format int32 */\n  totalBookCount?: number;\n  /** @format int64 */\n  sizeOnDisk?: number;\n  /** @format double */\n  percentOfBooks?: number;\n}\n\nexport interface BooksMonitoredResource {\n  bookIds?: number[] | null;\n  monitored?: boolean;\n}\n\nexport interface BookshelfAuthorResource {\n  /** @format int32 */\n  id?: number;\n  monitored?: boolean | null;\n  books?: BookResource[] | null;\n}\n\nexport interface BookshelfResource {\n  authors?: BookshelfAuthorResource[] | null;\n  monitoringOptions?: MonitoringOptions;\n  monitorNewItems?: NewItemMonitorTypes;\n}\n\nexport interface Command {\n  sendUpdatesToClient?: boolean;\n  updateScheduledTask?: boolean;\n  completionMessage?: string | null;\n  requiresDiskAccess?: boolean;\n  isExclusive?: boolean;\n  isTypeExclusive?: boolean;\n  isLongRunning?: boolean;\n  name?: string | null;\n  /** @format date-time */\n  lastExecutionTime?: string | null;\n  /** @format date-time */\n  lastStartTime?: string | null;\n  trigger?: CommandTrigger;\n  suppressMessages?: boolean;\n  clientUserAgent?: string | null;\n}\n\nexport interface CommandResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  commandName?: string | null;\n  message?: string | null;\n  body?: Command;\n  priority?: CommandPriority;\n  status?: CommandStatus;\n  result?: CommandResult;\n  /** @format date-time */\n  queued?: string;\n  /** @format date-time */\n  started?: string | null;\n  /** @format date-time */\n  ended?: string | null;\n  /** @format date-span */\n  duration?: string | null;\n  exception?: string | null;\n  trigger?: CommandTrigger;\n  clientUserAgent?: string | null;\n  /** @format date-time */\n  stateChangeTime?: string | null;\n  sendUpdatesToClient?: boolean;\n  updateScheduledTask?: boolean;\n  /** @format date-time */\n  lastExecutionTime?: string | null;\n}\n\nexport interface CustomFilterResource {\n  /** @format int32 */\n  id?: number;\n  type?: string | null;\n  label?: string | null;\n  filters?: Record<string, any>[] | null;\n}\n\nexport interface CustomFormat {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  includeCustomFormatWhenRenaming?: boolean;\n  specifications?: ICustomFormatSpecification[] | null;\n}\n\nexport interface CustomFormatResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  includeCustomFormatWhenRenaming?: boolean | null;\n  specifications?: CustomFormatSpecificationSchema[] | null;\n}\n\nexport interface CustomFormatSpecificationSchema {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  implementation?: string | null;\n  implementationName?: string | null;\n  infoLink?: string | null;\n  negate?: boolean;\n  required?: boolean;\n  fields?: Field[] | null;\n  presets?: CustomFormatSpecificationSchema[] | null;\n}\n\nexport interface DelayProfileResource {\n  /** @format int32 */\n  id?: number;\n  enableUsenet?: boolean;\n  enableTorrent?: boolean;\n  preferredProtocol?: DownloadProtocol;\n  /** @format int32 */\n  usenetDelay?: number;\n  /** @format int32 */\n  torrentDelay?: number;\n  bypassIfHighestQuality?: boolean;\n  bypassIfAboveCustomFormatScore?: boolean;\n  /** @format int32 */\n  minimumCustomFormatScore?: number;\n  /** @format int32 */\n  order?: number;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n}\n\nexport interface DevelopmentConfigResource {\n  /** @format int32 */\n  id?: number;\n  metadataSource?: string | null;\n  consoleLogLevel?: string | null;\n  logSql?: boolean;\n  /** @format int32 */\n  logRotate?: number;\n  filterSentryEvents?: boolean;\n}\n\nexport interface DiskSpaceResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  label?: string | null;\n  /** @format int64 */\n  freeSpace?: number;\n  /** @format int64 */\n  totalSpace?: number;\n}\n\nexport interface DownloadClientBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enable?: boolean | null;\n  /** @format int32 */\n  priority?: number | null;\n  removeCompletedDownloads?: boolean | null;\n  removeFailedDownloads?: boolean | null;\n}\n\nexport interface DownloadClientConfigResource {\n  /** @format int32 */\n  id?: number;\n  downloadClientWorkingFolders?: string | null;\n  enableCompletedDownloadHandling?: boolean;\n  autoRedownloadFailed?: boolean;\n  autoRedownloadFailedFromInteractiveSearch?: boolean;\n}\n\nexport interface DownloadClientResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: DownloadClientResource[] | null;\n  enable?: boolean;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  priority?: number;\n  removeCompletedDownloads?: boolean;\n  removeFailedDownloads?: boolean;\n}\n\nexport interface Edition {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  bookId?: number;\n  foreignEditionId?: string | null;\n  titleSlug?: string | null;\n  isbn13?: string | null;\n  asin?: string | null;\n  title?: string | null;\n  language?: string | null;\n  overview?: string | null;\n  format?: string | null;\n  isEbook?: boolean;\n  disambiguation?: string | null;\n  publisher?: string | null;\n  /** @format int32 */\n  pageCount?: number;\n  /** @format date-time */\n  releaseDate?: string | null;\n  images?: MediaCover[] | null;\n  links?: Links[] | null;\n  ratings?: Ratings;\n  monitored?: boolean;\n  manualAdd?: boolean;\n  book?: BookLazyLoaded;\n  bookFiles?: BookFileListLazyLoaded;\n}\n\nexport interface EditionLazyLoaded {\n  value?: Edition;\n  isLoaded?: boolean;\n}\n\nexport interface EditionListLazyLoaded {\n  value?: Edition[] | null;\n  isLoaded?: boolean;\n}\n\nexport interface EditionResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  bookId?: number;\n  foreignEditionId?: string | null;\n  titleSlug?: string | null;\n  isbn13?: string | null;\n  asin?: string | null;\n  title?: string | null;\n  language?: string | null;\n  overview?: string | null;\n  format?: string | null;\n  isEbook?: boolean;\n  disambiguation?: string | null;\n  publisher?: string | null;\n  /** @format int32 */\n  pageCount?: number;\n  /** @format date-time */\n  releaseDate?: string | null;\n  images?: MediaCover[] | null;\n  links?: Links[] | null;\n  ratings?: Ratings;\n  monitored?: boolean;\n  manualAdd?: boolean;\n  remoteCover?: string | null;\n}\n\nexport interface Field {\n  /** @format int32 */\n  order?: number;\n  name?: string | null;\n  label?: string | null;\n  unit?: string | null;\n  helpText?: string | null;\n  helpTextWarning?: string | null;\n  helpLink?: string | null;\n  value?: any;\n  type?: string | null;\n  advanced?: boolean;\n  selectOptions?: SelectOption[] | null;\n  selectOptionsProviderAction?: string | null;\n  section?: string | null;\n  hidden?: string | null;\n  placeholder?: string | null;\n  isFloat?: boolean;\n}\n\nexport interface HealthResource {\n  /** @format int32 */\n  id?: number;\n  source?: string | null;\n  type?: HealthCheckResult;\n  message?: string | null;\n  wikiUrl?: string | null;\n}\n\nexport interface HistoryResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  bookId?: number;\n  /** @format int32 */\n  authorId?: number;\n  sourceTitle?: string | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  qualityCutoffNotMet?: boolean;\n  /** @format date-time */\n  date?: string;\n  downloadId?: string | null;\n  eventType?: EntityHistoryEventType;\n  data?: Record<string, string | null>;\n  book?: BookResource;\n  author?: AuthorResource;\n}\n\nexport interface HistoryResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: HistoryResource[] | null;\n}\n\nexport interface HostConfigResource {\n  /** @format int32 */\n  id?: number;\n  bindAddress?: string | null;\n  /** @format int32 */\n  port?: number;\n  /** @format int32 */\n  sslPort?: number;\n  enableSsl?: boolean;\n  launchBrowser?: boolean;\n  authenticationMethod?: AuthenticationType;\n  authenticationRequired?: AuthenticationRequiredType;\n  analyticsEnabled?: boolean;\n  username?: string | null;\n  password?: string | null;\n  passwordConfirmation?: string | null;\n  logLevel?: string | null;\n  consoleLogLevel?: string | null;\n  branch?: string | null;\n  apiKey?: string | null;\n  sslCertPath?: string | null;\n  sslCertPassword?: string | null;\n  urlBase?: string | null;\n  instanceName?: string | null;\n  applicationUrl?: string | null;\n  updateAutomatically?: boolean;\n  updateMechanism?: UpdateMechanism;\n  updateScriptPath?: string | null;\n  proxyEnabled?: boolean;\n  proxyType?: ProxyType;\n  proxyHostname?: string | null;\n  /** @format int32 */\n  proxyPort?: number;\n  proxyUsername?: string | null;\n  proxyPassword?: string | null;\n  proxyBypassFilter?: string | null;\n  proxyBypassLocalAddresses?: boolean;\n  certificateValidation?: CertificateValidationType;\n  backupFolder?: string | null;\n  /** @format int32 */\n  backupInterval?: number;\n  /** @format int32 */\n  backupRetention?: number;\n  trustCgnatIpAddresses?: boolean;\n}\n\nexport interface ICustomFormatSpecification {\n  /** @format int32 */\n  order?: number;\n  infoLink?: string | null;\n  implementationName?: string | null;\n  name?: string | null;\n  negate?: boolean;\n  required?: boolean;\n}\n\nexport interface ImportListBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enableAutomaticAdd?: boolean | null;\n  rootFolderPath?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number | null;\n  /** @format int32 */\n  metadataProfileId?: number | null;\n}\n\nexport interface ImportListExclusionResource {\n  /** @format int32 */\n  id?: number;\n  foreignId?: string | null;\n  authorName?: string | null;\n}\n\nexport interface ImportListResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: ImportListResource[] | null;\n  enableAutomaticAdd?: boolean;\n  shouldMonitor?: ImportListMonitorType;\n  shouldMonitorExisting?: boolean;\n  shouldSearch?: boolean;\n  rootFolderPath?: string | null;\n  monitorNewItems?: NewItemMonitorTypes;\n  /** @format int32 */\n  qualityProfileId?: number;\n  /** @format int32 */\n  metadataProfileId?: number;\n  listType?: ImportListType;\n  /** @format int32 */\n  listOrder?: number;\n  /** @format date-span */\n  minRefreshInterval?: string;\n}\n\nexport interface IndexerBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enableRss?: boolean | null;\n  enableAutomaticSearch?: boolean | null;\n  enableInteractiveSearch?: boolean | null;\n  /** @format int32 */\n  priority?: number | null;\n}\n\nexport interface IndexerConfigResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  minimumAge?: number;\n  /** @format int32 */\n  maximumSize?: number;\n  /** @format int32 */\n  retention?: number;\n  /** @format int32 */\n  rssSyncInterval?: number;\n}\n\nexport interface IndexerFlagResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  nameLower?: string | null;\n}\n\nexport interface IndexerResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: IndexerResource[] | null;\n  enableRss?: boolean;\n  enableAutomaticSearch?: boolean;\n  enableInteractiveSearch?: boolean;\n  supportsRss?: boolean;\n  supportsSearch?: boolean;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  priority?: number;\n  /** @format int32 */\n  downloadClientId?: number;\n}\n\nexport interface IsoCountry {\n  twoLetterCode?: string | null;\n  name?: string | null;\n}\n\nexport interface LanguageResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  nameLower?: string | null;\n}\n\nexport interface Links {\n  url?: string | null;\n  name?: string | null;\n}\n\nexport interface LogFileResource {\n  /** @format int32 */\n  id?: number;\n  filename?: string | null;\n  /** @format date-time */\n  lastWriteTime?: string;\n  contentsUrl?: string | null;\n  downloadUrl?: string | null;\n}\n\nexport interface LogResource {\n  /** @format int32 */\n  id?: number;\n  /** @format date-time */\n  time?: string;\n  exception?: string | null;\n  exceptionType?: string | null;\n  level?: string | null;\n  logger?: string | null;\n  message?: string | null;\n  method?: string | null;\n}\n\nexport interface LogResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: LogResource[] | null;\n}\n\nexport interface ManualImportResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  name?: string | null;\n  /** @format int64 */\n  size?: number;\n  author?: AuthorResource;\n  book?: BookResource;\n  foreignEditionId?: string | null;\n  quality?: QualityModel;\n  releaseGroup?: string | null;\n  /** @format int32 */\n  qualityWeight?: number;\n  downloadId?: string | null;\n  /** @format int32 */\n  indexerFlags?: number;\n  rejections?: Rejection[] | null;\n  audioTags?: ParsedTrackInfo;\n  additionalFile?: boolean;\n  replaceExistingFiles?: boolean;\n  disableReleaseSwitching?: boolean;\n}\n\nexport interface ManualImportUpdateResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  name?: string | null;\n  /** @format int32 */\n  authorId?: number | null;\n  /** @format int32 */\n  bookId?: number | null;\n  foreignEditionId?: string | null;\n  quality?: QualityModel;\n  releaseGroup?: string | null;\n  /** @format int32 */\n  indexerFlags?: number;\n  downloadId?: string | null;\n  additionalFile?: boolean;\n  replaceExistingFiles?: boolean;\n  disableReleaseSwitching?: boolean;\n  rejections?: Rejection[] | null;\n}\n\nexport interface MediaCover {\n  url?: string | null;\n  coverType?: MediaCoverTypes;\n  extension?: string | null;\n  remoteUrl?: string | null;\n}\n\nexport interface MediaInfoModel {\n  audioFormat?: string | null;\n  /** @format int32 */\n  audioBitrate?: number;\n  /** @format int32 */\n  audioChannels?: number;\n  /** @format int32 */\n  audioBits?: number;\n  /** @format int32 */\n  audioSampleRate?: number;\n}\n\nexport interface MediaInfoResource {\n  /** @format int32 */\n  id?: number;\n  /** @format double */\n  audioChannels?: number;\n  audioBitRate?: string | null;\n  audioCodec?: string | null;\n  audioBits?: string | null;\n  audioSampleRate?: string | null;\n}\n\nexport interface MediaManagementConfigResource {\n  /** @format int32 */\n  id?: number;\n  autoUnmonitorPreviouslyDownloadedBooks?: boolean;\n  recycleBin?: string | null;\n  /** @format int32 */\n  recycleBinCleanupDays?: number;\n  downloadPropersAndRepacks?: ProperDownloadTypes;\n  createEmptyAuthorFolders?: boolean;\n  deleteEmptyFolders?: boolean;\n  fileDate?: FileDateType;\n  watchLibraryForChanges?: boolean;\n  rescanAfterRefresh?: RescanAfterRefreshType;\n  allowFingerprinting?: AllowFingerprinting;\n  setPermissionsLinux?: boolean;\n  chmodFolder?: string | null;\n  chownGroup?: string | null;\n  skipFreeSpaceCheckWhenImporting?: boolean;\n  /** @format int32 */\n  minimumFreeSpaceWhenImporting?: number;\n  copyUsingHardlinks?: boolean;\n  importExtraFiles?: boolean;\n  extraFileExtensions?: string | null;\n}\n\nexport interface MetadataProfile {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  /** @format double */\n  minPopularity?: number;\n  skipMissingDate?: boolean;\n  skipMissingIsbn?: boolean;\n  skipPartsAndSets?: boolean;\n  skipSeriesSecondary?: boolean;\n  allowedLanguages?: string | null;\n  /** @format int32 */\n  minPages?: number;\n  ignored?: string[] | null;\n}\n\nexport interface MetadataProfileLazyLoaded {\n  value?: MetadataProfile;\n  isLoaded?: boolean;\n}\n\nexport interface MetadataProfileResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  /** @format double */\n  minPopularity?: number;\n  skipMissingDate?: boolean;\n  skipMissingIsbn?: boolean;\n  skipPartsAndSets?: boolean;\n  skipSeriesSecondary?: boolean;\n  allowedLanguages?: string | null;\n  /** @format int32 */\n  minPages?: number;\n  ignored?: string[] | null;\n}\n\nexport interface MetadataProviderConfigResource {\n  /** @format int32 */\n  id?: number;\n  writeAudioTags?: WriteAudioTagsType;\n  scrubAudioTags?: boolean;\n  writeBookTags?: WriteBookTagsType;\n  updateCovers?: boolean;\n  embedMetadata?: boolean;\n}\n\nexport interface MetadataResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: MetadataResource[] | null;\n  enable?: boolean;\n}\n\nexport interface MonitoringOptions {\n  monitor?: MonitorTypes;\n  booksToMonitor?: string[] | null;\n  monitored?: boolean;\n}\n\nexport interface NamingConfigResource {\n  /** @format int32 */\n  id?: number;\n  renameBooks?: boolean;\n  replaceIllegalCharacters?: boolean;\n  /** @format int32 */\n  colonReplacementFormat?: number;\n  standardBookFormat?: string | null;\n  authorFolderFormat?: string | null;\n  includeAuthorName?: boolean;\n  includeBookTitle?: boolean;\n  includeQuality?: boolean;\n  replaceSpaces?: boolean;\n  separator?: string | null;\n  numberStyle?: string | null;\n}\n\nexport interface NotificationResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: NotificationResource[] | null;\n  link?: string | null;\n  onGrab?: boolean;\n  onReleaseImport?: boolean;\n  onUpgrade?: boolean;\n  onRename?: boolean;\n  onAuthorAdded?: boolean;\n  onAuthorDelete?: boolean;\n  onBookDelete?: boolean;\n  onBookFileDelete?: boolean;\n  onBookFileDeleteForUpgrade?: boolean;\n  onHealthIssue?: boolean;\n  onDownloadFailure?: boolean;\n  onImportFailure?: boolean;\n  onBookRetag?: boolean;\n  onApplicationUpdate?: boolean;\n  supportsOnGrab?: boolean;\n  supportsOnReleaseImport?: boolean;\n  supportsOnUpgrade?: boolean;\n  supportsOnRename?: boolean;\n  supportsOnAuthorAdded?: boolean;\n  supportsOnAuthorDelete?: boolean;\n  supportsOnBookDelete?: boolean;\n  supportsOnBookFileDelete?: boolean;\n  supportsOnBookFileDeleteForUpgrade?: boolean;\n  supportsOnHealthIssue?: boolean;\n  includeHealthWarnings?: boolean;\n  supportsOnDownloadFailure?: boolean;\n  supportsOnImportFailure?: boolean;\n  supportsOnBookRetag?: boolean;\n  supportsOnApplicationUpdate?: boolean;\n  testCommand?: string | null;\n}\n\nexport interface ParseResource {\n  /** @format int32 */\n  id?: number;\n  title?: string | null;\n  parsedBookInfo?: ParsedBookInfo;\n  author?: AuthorResource;\n  books?: BookResource[] | null;\n}\n\nexport interface ParsedBookInfo {\n  bookTitle?: string | null;\n  authorName?: string | null;\n  authorTitleInfo?: AuthorTitleInfo;\n  quality?: QualityModel;\n  releaseDate?: string | null;\n  discography?: boolean;\n  /** @format int32 */\n  discographyStart?: number;\n  /** @format int32 */\n  discographyEnd?: number;\n  releaseGroup?: string | null;\n  releaseHash?: string | null;\n  releaseVersion?: string | null;\n  releaseTitle?: string | null;\n}\n\nexport interface ParsedTrackInfo {\n  title?: string | null;\n  cleanTitle?: string | null;\n  authors?: string[] | null;\n  authorTitle?: string | null;\n  bookTitle?: string | null;\n  seriesTitle?: string | null;\n  seriesIndex?: string | null;\n  isbn?: string | null;\n  asin?: string | null;\n  goodreadsId?: string | null;\n  authorMBId?: string | null;\n  bookMBId?: string | null;\n  releaseMBId?: string | null;\n  recordingMBId?: string | null;\n  trackMBId?: string | null;\n  /** @format int32 */\n  discNumber?: number;\n  /** @format int32 */\n  discCount?: number;\n  country?: IsoCountry;\n  /** @format int32 */\n  year?: number;\n  publisher?: string | null;\n  label?: string | null;\n  source?: string | null;\n  catalogNumber?: string | null;\n  disambiguation?: string | null;\n  /** @format date-span */\n  duration?: string;\n  quality?: QualityModel;\n  mediaInfo?: MediaInfoModel;\n  trackNumbers?: number[] | null;\n  language?: string | null;\n  releaseGroup?: string | null;\n  releaseHash?: string | null;\n}\n\nexport interface PingResource {\n  status?: string | null;\n}\n\nexport interface ProfileFormatItem {\n  format?: CustomFormat;\n  /** @format int32 */\n  score?: number;\n}\n\nexport interface ProfileFormatItemResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  format?: number;\n  name?: string | null;\n  /** @format int32 */\n  score?: number;\n}\n\nexport interface ProviderMessage {\n  message?: string | null;\n  type?: ProviderMessageType;\n}\n\nexport interface Quality {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n}\n\nexport interface QualityDefinitionResource {\n  /** @format int32 */\n  id?: number;\n  quality?: Quality;\n  title?: string | null;\n  /** @format int32 */\n  weight?: number;\n  /** @format double */\n  minSize?: number | null;\n  /** @format double */\n  maxSize?: number | null;\n}\n\nexport interface QualityModel {\n  quality?: Quality;\n  revision?: Revision;\n}\n\nexport interface QualityProfile {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  upgradeAllowed?: boolean;\n  /** @format int32 */\n  cutoff?: number;\n  /** @format int32 */\n  minFormatScore?: number;\n  /** @format int32 */\n  cutoffFormatScore?: number;\n  formatItems?: ProfileFormatItem[] | null;\n  items?: QualityProfileQualityItem[] | null;\n}\n\nexport interface QualityProfileLazyLoaded {\n  value?: QualityProfile;\n  isLoaded?: boolean;\n}\n\nexport interface QualityProfileQualityItem {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  quality?: Quality;\n  items?: QualityProfileQualityItem[] | null;\n  allowed?: boolean;\n}\n\nexport interface QualityProfileQualityItemResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  quality?: Quality;\n  items?: QualityProfileQualityItemResource[] | null;\n  allowed?: boolean;\n}\n\nexport interface QualityProfileResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  upgradeAllowed?: boolean;\n  /** @format int32 */\n  cutoff?: number;\n  items?: QualityProfileQualityItemResource[] | null;\n  /** @format int32 */\n  minFormatScore?: number;\n  /** @format int32 */\n  cutoffFormatScore?: number;\n  formatItems?: ProfileFormatItemResource[] | null;\n}\n\nexport interface QueueBulkResource {\n  ids?: number[] | null;\n}\n\nexport interface QueueResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  authorId?: number | null;\n  /** @format int32 */\n  bookId?: number | null;\n  author?: AuthorResource;\n  book?: BookResource;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  /** @format double */\n  size?: number;\n  title?: string | null;\n  /** @format double */\n  sizeleft?: number;\n  /** @format date-span */\n  timeleft?: string | null;\n  /** @format date-time */\n  estimatedCompletionTime?: string | null;\n  status?: string | null;\n  trackedDownloadStatus?: TrackedDownloadStatus;\n  trackedDownloadState?: TrackedDownloadState;\n  statusMessages?: TrackedDownloadStatusMessage[] | null;\n  errorMessage?: string | null;\n  downloadId?: string | null;\n  protocol?: DownloadProtocol;\n  downloadClient?: string | null;\n  downloadClientHasPostImportCategory?: boolean;\n  indexer?: string | null;\n  outputPath?: string | null;\n  downloadForced?: boolean;\n}\n\nexport interface QueueResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: QueueResource[] | null;\n}\n\nexport interface QueueStatusResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  totalCount?: number;\n  /** @format int32 */\n  count?: number;\n  /** @format int32 */\n  unknownCount?: number;\n  errors?: boolean;\n  warnings?: boolean;\n  unknownErrors?: boolean;\n  unknownWarnings?: boolean;\n}\n\nexport interface Ratings {\n  /** @format int32 */\n  votes?: number;\n  /** @format double */\n  value?: number;\n  /** @format double */\n  popularity?: number;\n}\n\nexport interface Rejection {\n  reason?: string | null;\n  type?: RejectionType;\n}\n\nexport interface ReleaseProfileResource {\n  /** @format int32 */\n  id?: number;\n  enabled?: boolean;\n  required?: string[] | null;\n  ignored?: string[] | null;\n  /** @format int32 */\n  indexerId?: number;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n}\n\nexport interface ReleaseResource {\n  /** @format int32 */\n  id?: number;\n  guid?: string | null;\n  quality?: QualityModel;\n  /** @format int32 */\n  qualityWeight?: number;\n  /** @format int32 */\n  age?: number;\n  /** @format double */\n  ageHours?: number;\n  /** @format double */\n  ageMinutes?: number;\n  /** @format int64 */\n  size?: number;\n  /** @format int32 */\n  indexerId?: number;\n  indexer?: string | null;\n  releaseGroup?: string | null;\n  subGroup?: string | null;\n  releaseHash?: string | null;\n  title?: string | null;\n  discography?: boolean;\n  sceneSource?: boolean;\n  airDate?: string | null;\n  authorName?: string | null;\n  bookTitle?: string | null;\n  approved?: boolean;\n  temporarilyRejected?: boolean;\n  rejected?: boolean;\n  rejections?: string[] | null;\n  /** @format date-time */\n  publishDate?: string;\n  commentUrl?: string | null;\n  downloadUrl?: string | null;\n  infoUrl?: string | null;\n  downloadAllowed?: boolean;\n  /** @format int32 */\n  releaseWeight?: number;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  magnetUrl?: string | null;\n  infoHash?: string | null;\n  /** @format int32 */\n  seeders?: number | null;\n  /** @format int32 */\n  leechers?: number | null;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  indexerFlags?: number;\n  /** @format int32 */\n  authorId?: number | null;\n  /** @format int32 */\n  bookId?: number | null;\n  /** @format int32 */\n  downloadClientId?: number | null;\n  downloadClient?: string | null;\n}\n\nexport interface RemotePathMappingResource {\n  /** @format int32 */\n  id?: number;\n  host?: string | null;\n  remotePath?: string | null;\n  localPath?: string | null;\n}\n\nexport interface RenameBookResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  authorId?: number;\n  /** @format int32 */\n  bookId?: number;\n  /** @format int32 */\n  bookFileId?: number;\n  existingPath?: string | null;\n  newPath?: string | null;\n}\n\nexport interface RetagBookResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  authorId?: number;\n  /** @format int32 */\n  bookId?: number;\n  trackNumbers?: number[] | null;\n  /** @format int32 */\n  bookFileId?: number;\n  path?: string | null;\n  changes?: TagDifference[] | null;\n}\n\nexport interface Revision {\n  /** @format int32 */\n  version?: number;\n  /** @format int32 */\n  real?: number;\n  isRepack?: boolean;\n}\n\nexport interface RootFolderResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  path?: string | null;\n  /** @format int32 */\n  defaultMetadataProfileId?: number;\n  /** @format int32 */\n  defaultQualityProfileId?: number;\n  defaultMonitorOption?: MonitorTypes;\n  defaultNewItemMonitorOption?: NewItemMonitorTypes;\n  /** @uniqueItems true */\n  defaultTags?: number[] | null;\n  isCalibreLibrary?: boolean;\n  host?: string | null;\n  /** @format int32 */\n  port?: number;\n  urlBase?: string | null;\n  username?: string | null;\n  password?: string | null;\n  library?: string | null;\n  outputFormat?: string | null;\n  outputProfile?: string | null;\n  useSsl?: boolean;\n  accessible?: boolean;\n  /** @format int64 */\n  freeSpace?: number | null;\n  /** @format int64 */\n  totalSpace?: number | null;\n}\n\nexport interface SelectOption {\n  /** @format int32 */\n  value?: number;\n  name?: string | null;\n  /** @format int32 */\n  order?: number;\n  hint?: string | null;\n}\n\nexport interface Series {\n  /** @format int32 */\n  id?: number;\n  foreignSeriesId?: string | null;\n  title?: string | null;\n  description?: string | null;\n  numbered?: boolean;\n  /** @format int32 */\n  workCount?: number;\n  /** @format int32 */\n  primaryWorkCount?: number;\n  linkItems?: SeriesBookLinkListLazyLoaded;\n  books?: BookListLazyLoaded;\n  foreignAuthorId?: string | null;\n}\n\nexport interface SeriesBookLink {\n  /** @format int32 */\n  id?: number;\n  position?: string | null;\n  /** @format int32 */\n  seriesPosition?: number;\n  /** @format int32 */\n  seriesId?: number;\n  /** @format int32 */\n  bookId?: number;\n  isPrimary?: boolean;\n  series?: SeriesLazyLoaded;\n  book?: BookLazyLoaded;\n}\n\nexport interface SeriesBookLinkListLazyLoaded {\n  value?: SeriesBookLink[] | null;\n  isLoaded?: boolean;\n}\n\nexport interface SeriesBookLinkResource {\n  /** @format int32 */\n  id?: number;\n  position?: string | null;\n  /** @format int32 */\n  seriesPosition?: number;\n  /** @format int32 */\n  seriesId?: number;\n  /** @format int32 */\n  bookId?: number;\n}\n\nexport interface SeriesLazyLoaded {\n  value?: Series;\n  isLoaded?: boolean;\n}\n\nexport interface SeriesListLazyLoaded {\n  value?: Series[] | null;\n  isLoaded?: boolean;\n}\n\nexport interface SeriesResource {\n  /** @format int32 */\n  id?: number;\n  title?: string | null;\n  description?: string | null;\n  links?: SeriesBookLinkResource[] | null;\n}\n\nexport interface SystemResource {\n  appName?: string | null;\n  instanceName?: string | null;\n  version?: string | null;\n  /** @format date-time */\n  buildTime?: string;\n  isDebug?: boolean;\n  isProduction?: boolean;\n  isAdmin?: boolean;\n  isUserInteractive?: boolean;\n  startupPath?: string | null;\n  appData?: string | null;\n  osName?: string | null;\n  osVersion?: string | null;\n  isNetCore?: boolean;\n  isLinux?: boolean;\n  isOsx?: boolean;\n  isWindows?: boolean;\n  isDocker?: boolean;\n  mode?: RuntimeMode;\n  branch?: string | null;\n  databaseType?: DatabaseType;\n  databaseVersion?: string | null;\n  authentication?: AuthenticationType;\n  /** @format int32 */\n  migrationVersion?: number;\n  urlBase?: string | null;\n  runtimeVersion?: string | null;\n  runtimeName?: string | null;\n  /** @format date-time */\n  startTime?: string;\n  packageVersion?: string | null;\n  packageAuthor?: string | null;\n  packageUpdateMechanism?: UpdateMechanism;\n  packageUpdateMechanismMessage?: string | null;\n}\n\nexport interface TagDetailsResource {\n  /** @format int32 */\n  id?: number;\n  label?: string | null;\n  delayProfileIds?: number[] | null;\n  importListIds?: number[] | null;\n  notificationIds?: number[] | null;\n  restrictionIds?: number[] | null;\n  indexerIds?: number[] | null;\n  downloadClientIds?: number[] | null;\n  authorIds?: number[] | null;\n}\n\nexport interface TagDifference {\n  field?: string | null;\n  oldValue?: string | null;\n  newValue?: string | null;\n}\n\nexport interface TagResource {\n  /** @format int32 */\n  id?: number;\n  label?: string | null;\n}\n\nexport interface TaskResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  taskName?: string | null;\n  /** @format int32 */\n  interval?: number;\n  /** @format date-time */\n  lastExecution?: string;\n  /** @format date-time */\n  lastStartTime?: string;\n  /** @format date-time */\n  nextExecution?: string;\n  /** @format date-span */\n  lastDuration?: string;\n}\n\nexport interface TrackedDownloadStatusMessage {\n  title?: string | null;\n  messages?: string[] | null;\n}\n\nexport interface UiConfigResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  firstDayOfWeek?: number;\n  calendarWeekColumnHeader?: string | null;\n  shortDateFormat?: string | null;\n  longDateFormat?: string | null;\n  timeFormat?: string | null;\n  showRelativeDates?: boolean;\n  enableColorImpairedMode?: boolean;\n  /** @format int32 */\n  uiLanguage?: number;\n  theme?: string | null;\n}\n\nexport interface UpdateChanges {\n  new?: string[] | null;\n  fixed?: string[] | null;\n}\n\nexport interface UpdateResource {\n  /** @format int32 */\n  id?: number;\n  version?: string | null;\n  branch?: string | null;\n  /** @format date-time */\n  releaseDate?: string;\n  fileName?: string | null;\n  url?: string | null;\n  installed?: boolean;\n  /** @format date-time */\n  installedOn?: string | null;\n  installable?: boolean;\n  latest?: boolean;\n  changes?: UpdateChanges;\n  hash?: string | null;\n}\n"
  },
  {
    "path": "src/__generated__/sonarr/Api.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { ContentType, HttpClient, RequestParams } from \"./../../ky-client\";\nimport {\n  AutoTaggingResource,\n  BackupResource,\n  BlocklistBulkResource,\n  BlocklistResourcePagingResource,\n  CommandResource,\n  CustomFilterResource,\n  CustomFormatBulkResource,\n  CustomFormatResource,\n  DelayProfileResource,\n  DiskSpaceResource,\n  DownloadClientBulkResource,\n  DownloadClientConfigResource,\n  DownloadClientResource,\n  DownloadProtocol,\n  EpisodeFileListResource,\n  EpisodeFileResource,\n  EpisodeHistoryEventType,\n  EpisodeResource,\n  EpisodeResourcePagingResource,\n  EpisodesMonitoredResource,\n  HealthResource,\n  HistoryResource,\n  HistoryResourcePagingResource,\n  HostConfigResource,\n  ImportListBulkResource,\n  ImportListConfigResource,\n  ImportListExclusionBulkResource,\n  ImportListExclusionResource,\n  ImportListExclusionResourcePagingResource,\n  ImportListResource,\n  IndexerBulkResource,\n  IndexerConfigResource,\n  IndexerFlagResource,\n  IndexerResource,\n  LanguageProfileResource,\n  LanguageResource,\n  LocalizationLanguageResource,\n  LocalizationResource,\n  LogFileResource,\n  LogResourcePagingResource,\n  ManualImportReprocessResource,\n  ManualImportResource,\n  MediaManagementConfigResource,\n  MetadataResource,\n  NamingConfigResource,\n  NotificationResource,\n  ParseResource,\n  QualityDefinitionLimitsResource,\n  QualityDefinitionResource,\n  QualityProfileResource,\n  QueueBulkResource,\n  QueueResource,\n  QueueResourcePagingResource,\n  QueueStatus,\n  QueueStatusResource,\n  ReleaseProfileResource,\n  ReleaseResource,\n  RemotePathMappingResource,\n  RenameEpisodeResource,\n  RootFolderResource,\n  SeasonPassResource,\n  SeriesEditorResource,\n  SeriesResource,\n  SortDirection,\n  SystemResource,\n  TagDetailsResource,\n  TagResource,\n  TaskResource,\n  UiConfigResource,\n  UpdateResource,\n} from \"./data-contracts\";\n\nexport class Api<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags ApiInfo\n   * @name GetApi\n   * @request GET:/api\n   * @secure\n   */\n  getApi = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingCreate\n   * @request POST:/api/v3/autotagging\n   * @secure\n   */\n  v3AutotaggingCreate = (data: AutoTaggingResource, params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource, any>({\n      path: `/api/v3/autotagging`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingList\n   * @request GET:/api/v3/autotagging\n   * @secure\n   */\n  v3AutotaggingList = (params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource[], any>({\n      path: `/api/v3/autotagging`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingUpdate\n   * @request PUT:/api/v3/autotagging/{id}\n   * @secure\n   */\n  v3AutotaggingUpdate = (id: string, data: AutoTaggingResource, params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource, any>({\n      path: `/api/v3/autotagging/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingDelete\n   * @request DELETE:/api/v3/autotagging/{id}\n   * @secure\n   */\n  v3AutotaggingDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/autotagging/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingDetail\n   * @request GET:/api/v3/autotagging/{id}\n   * @secure\n   */\n  v3AutotaggingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource, any>({\n      path: `/api/v3/autotagging/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingSchemaList\n   * @request GET:/api/v3/autotagging/schema\n   * @secure\n   */\n  v3AutotaggingSchemaList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/autotagging/schema`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V3SystemBackupList\n   * @request GET:/api/v3/system/backup\n   * @secure\n   */\n  v3SystemBackupList = (params: RequestParams = {}) =>\n    this.http.request<BackupResource[], any>({\n      path: `/api/v3/system/backup`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V3SystemBackupDelete\n   * @request DELETE:/api/v3/system/backup/{id}\n   * @secure\n   */\n  v3SystemBackupDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/backup/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V3SystemBackupRestoreCreate\n   * @request POST:/api/v3/system/backup/restore/{id}\n   * @secure\n   */\n  v3SystemBackupRestoreCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/backup/restore/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V3SystemBackupRestoreUploadCreate\n   * @request POST:/api/v3/system/backup/restore/upload\n   * @secure\n   */\n  v3SystemBackupRestoreUploadCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/backup/restore/upload`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V3BlocklistList\n   * @request GET:/api/v3/blocklist\n   * @secure\n   */\n  v3BlocklistList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      seriesIds?: number[];\n      protocols?: DownloadProtocol[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<BlocklistResourcePagingResource, any>({\n      path: `/api/v3/blocklist`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V3BlocklistDelete\n   * @request DELETE:/api/v3/blocklist/{id}\n   * @secure\n   */\n  v3BlocklistDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/blocklist/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V3BlocklistBulkDelete\n   * @request DELETE:/api/v3/blocklist/bulk\n   * @secure\n   */\n  v3BlocklistBulkDelete = (data: BlocklistBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/blocklist/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Calendar\n   * @name V3CalendarList\n   * @request GET:/api/v3/calendar\n   * @secure\n   */\n  v3CalendarList = (\n    query?: {\n      /** @format date-time */\n      start?: string;\n      /** @format date-time */\n      end?: string;\n      /** @default false */\n      unmonitored?: boolean;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisodeFile?: boolean;\n      /** @default false */\n      includeEpisodeImages?: boolean;\n      /** @default \"\" */\n      tags?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<EpisodeResource[], any>({\n      path: `/api/v3/calendar`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Calendar\n   * @name V3CalendarDetail\n   * @request GET:/api/v3/calendar/{id}\n   * @secure\n   */\n  v3CalendarDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<EpisodeResource, any>({\n      path: `/api/v3/calendar/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V3CommandCreate\n   * @request POST:/api/v3/command\n   * @secure\n   */\n  v3CommandCreate = (data: CommandResource, params: RequestParams = {}) =>\n    this.http.request<CommandResource, any>({\n      path: `/api/v3/command`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V3CommandList\n   * @request GET:/api/v3/command\n   * @secure\n   */\n  v3CommandList = (params: RequestParams = {}) =>\n    this.http.request<CommandResource[], any>({\n      path: `/api/v3/command`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V3CommandDelete\n   * @request DELETE:/api/v3/command/{id}\n   * @secure\n   */\n  v3CommandDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/command/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V3CommandDetail\n   * @request GET:/api/v3/command/{id}\n   * @secure\n   */\n  v3CommandDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CommandResource, any>({\n      path: `/api/v3/command/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterList\n   * @request GET:/api/v3/customfilter\n   * @secure\n   */\n  v3CustomfilterList = (params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource[], any>({\n      path: `/api/v3/customfilter`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterCreate\n   * @request POST:/api/v3/customfilter\n   * @secure\n   */\n  v3CustomfilterCreate = (data: CustomFilterResource, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v3/customfilter`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterUpdate\n   * @request PUT:/api/v3/customfilter/{id}\n   * @secure\n   */\n  v3CustomfilterUpdate = (id: string, data: CustomFilterResource, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v3/customfilter/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterDelete\n   * @request DELETE:/api/v3/customfilter/{id}\n   * @secure\n   */\n  v3CustomfilterDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/customfilter/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterDetail\n   * @request GET:/api/v3/customfilter/{id}\n   * @secure\n   */\n  v3CustomfilterDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v3/customfilter/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatList\n   * @request GET:/api/v3/customformat\n   * @secure\n   */\n  v3CustomformatList = (params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource[], any>({\n      path: `/api/v3/customformat`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatCreate\n   * @request POST:/api/v3/customformat\n   * @secure\n   */\n  v3CustomformatCreate = (data: CustomFormatResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v3/customformat`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatUpdate\n   * @request PUT:/api/v3/customformat/{id}\n   * @secure\n   */\n  v3CustomformatUpdate = (id: string, data: CustomFormatResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v3/customformat/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatDelete\n   * @request DELETE:/api/v3/customformat/{id}\n   * @secure\n   */\n  v3CustomformatDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/customformat/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatDetail\n   * @request GET:/api/v3/customformat/{id}\n   * @secure\n   */\n  v3CustomformatDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v3/customformat/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatBulkUpdate\n   * @request PUT:/api/v3/customformat/bulk\n   * @secure\n   */\n  v3CustomformatBulkUpdate = (data: CustomFormatBulkResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v3/customformat/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatBulkDelete\n   * @request DELETE:/api/v3/customformat/bulk\n   * @secure\n   */\n  v3CustomformatBulkDelete = (data: CustomFormatBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/customformat/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatSchemaList\n   * @request GET:/api/v3/customformat/schema\n   * @secure\n   */\n  v3CustomformatSchemaList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/customformat/schema`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Cutoff\n   * @name V3WantedCutoffList\n   * @request GET:/api/v3/wanted/cutoff\n   * @secure\n   */\n  v3WantedCutoffList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisodeFile?: boolean;\n      /** @default false */\n      includeImages?: boolean;\n      /** @default true */\n      monitored?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<EpisodeResourcePagingResource, any>({\n      path: `/api/v3/wanted/cutoff`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Cutoff\n   * @name V3WantedCutoffDetail\n   * @request GET:/api/v3/wanted/cutoff/{id}\n   * @secure\n   */\n  v3WantedCutoffDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<EpisodeResource, any>({\n      path: `/api/v3/wanted/cutoff/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileCreate\n   * @request POST:/api/v3/delayprofile\n   * @secure\n   */\n  v3DelayprofileCreate = (data: DelayProfileResource, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v3/delayprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileList\n   * @request GET:/api/v3/delayprofile\n   * @secure\n   */\n  v3DelayprofileList = (params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource[], any>({\n      path: `/api/v3/delayprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileDelete\n   * @request DELETE:/api/v3/delayprofile/{id}\n   * @secure\n   */\n  v3DelayprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/delayprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileUpdate\n   * @request PUT:/api/v3/delayprofile/{id}\n   * @secure\n   */\n  v3DelayprofileUpdate = (id: string, data: DelayProfileResource, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v3/delayprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileDetail\n   * @request GET:/api/v3/delayprofile/{id}\n   * @secure\n   */\n  v3DelayprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v3/delayprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileReorderUpdate\n   * @request PUT:/api/v3/delayprofile/reorder/{id}\n   * @secure\n   */\n  v3DelayprofileReorderUpdate = (\n    id: number,\n    query?: {\n      /** @format int32 */\n      after?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DelayProfileResource[], any>({\n      path: `/api/v3/delayprofile/reorder/${id}`,\n      method: \"PUT\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DiskSpace\n   * @name V3DiskspaceList\n   * @request GET:/api/v3/diskspace\n   * @secure\n   */\n  v3DiskspaceList = (params: RequestParams = {}) =>\n    this.http.request<DiskSpaceResource[], any>({\n      path: `/api/v3/diskspace`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientList\n   * @request GET:/api/v3/downloadclient\n   * @secure\n   */\n  v3DownloadclientList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource[], any>({\n      path: `/api/v3/downloadclient`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientCreate\n   * @request POST:/api/v3/downloadclient\n   * @secure\n   */\n  v3DownloadclientCreate = (\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v3/downloadclient`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientUpdate\n   * @request PUT:/api/v3/downloadclient/{id}\n   * @secure\n   */\n  v3DownloadclientUpdate = (\n    id: number,\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v3/downloadclient/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientDelete\n   * @request DELETE:/api/v3/downloadclient/{id}\n   * @secure\n   */\n  v3DownloadclientDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientDetail\n   * @request GET:/api/v3/downloadclient/{id}\n   * @secure\n   */\n  v3DownloadclientDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v3/downloadclient/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientBulkUpdate\n   * @request PUT:/api/v3/downloadclient/bulk\n   * @secure\n   */\n  v3DownloadclientBulkUpdate = (data: DownloadClientBulkResource, params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v3/downloadclient/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientBulkDelete\n   * @request DELETE:/api/v3/downloadclient/bulk\n   * @secure\n   */\n  v3DownloadclientBulkDelete = (data: DownloadClientBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientSchemaList\n   * @request GET:/api/v3/downloadclient/schema\n   * @secure\n   */\n  v3DownloadclientSchemaList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource[], any>({\n      path: `/api/v3/downloadclient/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientTestCreate\n   * @request POST:/api/v3/downloadclient/test\n   * @secure\n   */\n  v3DownloadclientTestCreate = (\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientTestallCreate\n   * @request POST:/api/v3/downloadclient/testall\n   * @secure\n   */\n  v3DownloadclientTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientActionCreate\n   * @request POST:/api/v3/downloadclient/action/{name}\n   * @secure\n   */\n  v3DownloadclientActionCreate = (name: string, data: DownloadClientResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V3ConfigDownloadclientList\n   * @request GET:/api/v3/config/downloadclient\n   * @secure\n   */\n  v3ConfigDownloadclientList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v3/config/downloadclient`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V3ConfigDownloadclientUpdate\n   * @request PUT:/api/v3/config/downloadclient/{id}\n   * @secure\n   */\n  v3ConfigDownloadclientUpdate = (id: string, data: DownloadClientConfigResource, params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v3/config/downloadclient/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V3ConfigDownloadclientDetail\n   * @request GET:/api/v3/config/downloadclient/{id}\n   * @secure\n   */\n  v3ConfigDownloadclientDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v3/config/downloadclient/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Episode\n   * @name V3EpisodeList\n   * @request GET:/api/v3/episode\n   * @secure\n   */\n  v3EpisodeList = (\n    query?: {\n      /** @format int32 */\n      seriesId?: number;\n      /** @format int32 */\n      seasonNumber?: number;\n      episodeIds?: number[];\n      /** @format int32 */\n      episodeFileId?: number;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisodeFile?: boolean;\n      /** @default false */\n      includeImages?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<EpisodeResource[], any>({\n      path: `/api/v3/episode`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Episode\n   * @name V3EpisodeUpdate\n   * @request PUT:/api/v3/episode/{id}\n   * @secure\n   */\n  v3EpisodeUpdate = (id: number, data: EpisodeResource, params: RequestParams = {}) =>\n    this.http.request<EpisodeResource, any>({\n      path: `/api/v3/episode/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Episode\n   * @name V3EpisodeDetail\n   * @request GET:/api/v3/episode/{id}\n   * @secure\n   */\n  v3EpisodeDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<EpisodeResource, any>({\n      path: `/api/v3/episode/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Episode\n   * @name V3EpisodeMonitorUpdate\n   * @request PUT:/api/v3/episode/monitor\n   * @secure\n   */\n  v3EpisodeMonitorUpdate = (\n    data: EpisodesMonitoredResource,\n    query?: {\n      /** @default false */\n      includeImages?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/episode/monitor`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileList\n   * @request GET:/api/v3/episodefile\n   * @secure\n   */\n  v3EpisodefileList = (\n    query?: {\n      /** @format int32 */\n      seriesId?: number;\n      episodeFileIds?: number[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<EpisodeFileResource[], any>({\n      path: `/api/v3/episodefile`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileUpdate\n   * @request PUT:/api/v3/episodefile/{id}\n   * @secure\n   */\n  v3EpisodefileUpdate = (id: string, data: EpisodeFileResource, params: RequestParams = {}) =>\n    this.http.request<EpisodeFileResource, any>({\n      path: `/api/v3/episodefile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileDelete\n   * @request DELETE:/api/v3/episodefile/{id}\n   * @secure\n   */\n  v3EpisodefileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/episodefile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileDetail\n   * @request GET:/api/v3/episodefile/{id}\n   * @secure\n   */\n  v3EpisodefileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<EpisodeFileResource, any>({\n      path: `/api/v3/episodefile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileEditorUpdate\n   * @request PUT:/api/v3/episodefile/editor\n   * @secure\n   */\n  v3EpisodefileEditorUpdate = (data: EpisodeFileListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/episodefile/editor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileBulkDelete\n   * @request DELETE:/api/v3/episodefile/bulk\n   * @secure\n   */\n  v3EpisodefileBulkDelete = (data: EpisodeFileListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/episodefile/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileBulkUpdate\n   * @request PUT:/api/v3/episodefile/bulk\n   * @secure\n   */\n  v3EpisodefileBulkUpdate = (data: EpisodeFileResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/episodefile/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V3FilesystemList\n   * @request GET:/api/v3/filesystem\n   * @secure\n   */\n  v3FilesystemList = (\n    query?: {\n      path?: string;\n      /** @default false */\n      includeFiles?: boolean;\n      /** @default false */\n      allowFoldersWithoutTrailingSlashes?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/filesystem`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V3FilesystemTypeList\n   * @request GET:/api/v3/filesystem/type\n   * @secure\n   */\n  v3FilesystemTypeList = (\n    query?: {\n      path?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/filesystem/type`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V3FilesystemMediafilesList\n   * @request GET:/api/v3/filesystem/mediafiles\n   * @secure\n   */\n  v3FilesystemMediafilesList = (\n    query?: {\n      path?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/filesystem/mediafiles`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Health\n   * @name V3HealthList\n   * @request GET:/api/v3/health\n   * @secure\n   */\n  v3HealthList = (params: RequestParams = {}) =>\n    this.http.request<HealthResource[], any>({\n      path: `/api/v3/health`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V3HistoryList\n   * @request GET:/api/v3/history\n   * @secure\n   */\n  v3HistoryList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      includeSeries?: boolean;\n      includeEpisode?: boolean;\n      eventType?: number[];\n      /** @format int32 */\n      episodeId?: number;\n      downloadId?: string;\n      seriesIds?: number[];\n      languages?: number[];\n      quality?: number[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResourcePagingResource, any>({\n      path: `/api/v3/history`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V3HistorySinceList\n   * @request GET:/api/v3/history/since\n   * @secure\n   */\n  v3HistorySinceList = (\n    query?: {\n      /** @format date-time */\n      date?: string;\n      eventType?: EpisodeHistoryEventType;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisode?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResource[], any>({\n      path: `/api/v3/history/since`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V3HistorySeriesList\n   * @request GET:/api/v3/history/series\n   * @secure\n   */\n  v3HistorySeriesList = (\n    query?: {\n      /** @format int32 */\n      seriesId?: number;\n      /** @format int32 */\n      seasonNumber?: number;\n      eventType?: EpisodeHistoryEventType;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisode?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResource[], any>({\n      path: `/api/v3/history/series`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V3HistoryFailedCreate\n   * @request POST:/api/v3/history/failed/{id}\n   * @secure\n   */\n  v3HistoryFailedCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/history/failed/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V3ConfigHostList\n   * @request GET:/api/v3/config/host\n   * @secure\n   */\n  v3ConfigHostList = (params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v3/config/host`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V3ConfigHostUpdate\n   * @request PUT:/api/v3/config/host/{id}\n   * @secure\n   */\n  v3ConfigHostUpdate = (id: string, data: HostConfigResource, params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v3/config/host/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V3ConfigHostDetail\n   * @request GET:/api/v3/config/host/{id}\n   * @secure\n   */\n  v3ConfigHostDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v3/config/host/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistList\n   * @request GET:/api/v3/importlist\n   * @secure\n   */\n  v3ImportlistList = (params: RequestParams = {}) =>\n    this.http.request<ImportListResource[], any>({\n      path: `/api/v3/importlist`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistCreate\n   * @request POST:/api/v3/importlist\n   * @secure\n   */\n  v3ImportlistCreate = (\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v3/importlist`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistUpdate\n   * @request PUT:/api/v3/importlist/{id}\n   * @secure\n   */\n  v3ImportlistUpdate = (\n    id: number,\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v3/importlist/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistDelete\n   * @request DELETE:/api/v3/importlist/{id}\n   * @secure\n   */\n  v3ImportlistDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistDetail\n   * @request GET:/api/v3/importlist/{id}\n   * @secure\n   */\n  v3ImportlistDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v3/importlist/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistBulkUpdate\n   * @request PUT:/api/v3/importlist/bulk\n   * @secure\n   */\n  v3ImportlistBulkUpdate = (data: ImportListBulkResource, params: RequestParams = {}) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v3/importlist/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistBulkDelete\n   * @request DELETE:/api/v3/importlist/bulk\n   * @secure\n   */\n  v3ImportlistBulkDelete = (data: ImportListBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistSchemaList\n   * @request GET:/api/v3/importlist/schema\n   * @secure\n   */\n  v3ImportlistSchemaList = (params: RequestParams = {}) =>\n    this.http.request<ImportListResource[], any>({\n      path: `/api/v3/importlist/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistTestCreate\n   * @request POST:/api/v3/importlist/test\n   * @secure\n   */\n  v3ImportlistTestCreate = (\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistTestallCreate\n   * @request POST:/api/v3/importlist/testall\n   * @secure\n   */\n  v3ImportlistTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistActionCreate\n   * @request POST:/api/v3/importlist/action/{name}\n   * @secure\n   */\n  v3ImportlistActionCreate = (name: string, data: ImportListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListConfig\n   * @name V3ConfigImportlistList\n   * @request GET:/api/v3/config/importlist\n   * @secure\n   */\n  v3ConfigImportlistList = (params: RequestParams = {}) =>\n    this.http.request<ImportListConfigResource, any>({\n      path: `/api/v3/config/importlist`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListConfig\n   * @name V3ConfigImportlistUpdate\n   * @request PUT:/api/v3/config/importlist/{id}\n   * @secure\n   */\n  v3ConfigImportlistUpdate = (id: string, data: ImportListConfigResource, params: RequestParams = {}) =>\n    this.http.request<ImportListConfigResource, any>({\n      path: `/api/v3/config/importlist/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListConfig\n   * @name V3ConfigImportlistDetail\n   * @request GET:/api/v3/config/importlist/{id}\n   * @secure\n   */\n  v3ConfigImportlistDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListConfigResource, any>({\n      path: `/api/v3/config/importlist/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ImportlistexclusionList\n   * @request GET:/api/v3/importlistexclusion\n   * @deprecated\n   * @secure\n   */\n  v3ImportlistexclusionList = (params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource[], any>({\n      path: `/api/v3/importlistexclusion`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ImportlistexclusionCreate\n   * @request POST:/api/v3/importlistexclusion\n   * @secure\n   */\n  v3ImportlistexclusionCreate = (data: ImportListExclusionResource, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v3/importlistexclusion`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ImportlistexclusionPagedList\n   * @request GET:/api/v3/importlistexclusion/paged\n   * @secure\n   */\n  v3ImportlistexclusionPagedList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ImportListExclusionResourcePagingResource, any>({\n      path: `/api/v3/importlistexclusion/paged`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ImportlistexclusionUpdate\n   * @request PUT:/api/v3/importlistexclusion/{id}\n   * @secure\n   */\n  v3ImportlistexclusionUpdate = (id: string, data: ImportListExclusionResource, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v3/importlistexclusion/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ImportlistexclusionDelete\n   * @request DELETE:/api/v3/importlistexclusion/{id}\n   * @secure\n   */\n  v3ImportlistexclusionDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlistexclusion/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ImportlistexclusionDetail\n   * @request GET:/api/v3/importlistexclusion/{id}\n   * @secure\n   */\n  v3ImportlistexclusionDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v3/importlistexclusion/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ImportlistexclusionBulkDelete\n   * @request DELETE:/api/v3/importlistexclusion/bulk\n   * @secure\n   */\n  v3ImportlistexclusionBulkDelete = (data: ImportListExclusionBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlistexclusion/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerList\n   * @request GET:/api/v3/indexer\n   * @secure\n   */\n  v3IndexerList = (params: RequestParams = {}) =>\n    this.http.request<IndexerResource[], any>({\n      path: `/api/v3/indexer`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerCreate\n   * @request POST:/api/v3/indexer\n   * @secure\n   */\n  v3IndexerCreate = (\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v3/indexer`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerUpdate\n   * @request PUT:/api/v3/indexer/{id}\n   * @secure\n   */\n  v3IndexerUpdate = (\n    id: number,\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v3/indexer/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerDelete\n   * @request DELETE:/api/v3/indexer/{id}\n   * @secure\n   */\n  v3IndexerDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerDetail\n   * @request GET:/api/v3/indexer/{id}\n   * @secure\n   */\n  v3IndexerDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v3/indexer/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerBulkUpdate\n   * @request PUT:/api/v3/indexer/bulk\n   * @secure\n   */\n  v3IndexerBulkUpdate = (data: IndexerBulkResource, params: RequestParams = {}) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v3/indexer/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerBulkDelete\n   * @request DELETE:/api/v3/indexer/bulk\n   * @secure\n   */\n  v3IndexerBulkDelete = (data: IndexerBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerSchemaList\n   * @request GET:/api/v3/indexer/schema\n   * @secure\n   */\n  v3IndexerSchemaList = (params: RequestParams = {}) =>\n    this.http.request<IndexerResource[], any>({\n      path: `/api/v3/indexer/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerTestCreate\n   * @request POST:/api/v3/indexer/test\n   * @secure\n   */\n  v3IndexerTestCreate = (\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerTestallCreate\n   * @request POST:/api/v3/indexer/testall\n   * @secure\n   */\n  v3IndexerTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerActionCreate\n   * @request POST:/api/v3/indexer/action/{name}\n   * @secure\n   */\n  v3IndexerActionCreate = (name: string, data: IndexerResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V3ConfigIndexerList\n   * @request GET:/api/v3/config/indexer\n   * @secure\n   */\n  v3ConfigIndexerList = (params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v3/config/indexer`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V3ConfigIndexerUpdate\n   * @request PUT:/api/v3/config/indexer/{id}\n   * @secure\n   */\n  v3ConfigIndexerUpdate = (id: string, data: IndexerConfigResource, params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v3/config/indexer/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V3ConfigIndexerDetail\n   * @request GET:/api/v3/config/indexer/{id}\n   * @secure\n   */\n  v3ConfigIndexerDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v3/config/indexer/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerFlag\n   * @name V3IndexerflagList\n   * @request GET:/api/v3/indexerflag\n   * @secure\n   */\n  v3IndexerflagList = (params: RequestParams = {}) =>\n    this.http.request<IndexerFlagResource[], any>({\n      path: `/api/v3/indexerflag`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Language\n   * @name V3LanguageList\n   * @request GET:/api/v3/language\n   * @secure\n   */\n  v3LanguageList = (params: RequestParams = {}) =>\n    this.http.request<LanguageResource[], any>({\n      path: `/api/v3/language`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Language\n   * @name V3LanguageDetail\n   * @request GET:/api/v3/language/{id}\n   * @secure\n   */\n  v3LanguageDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<LanguageResource, any>({\n      path: `/api/v3/language/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LanguageProfile\n   * @name V3LanguageprofileCreate\n   * @request POST:/api/v3/languageprofile\n   * @deprecated\n   * @secure\n   */\n  v3LanguageprofileCreate = (data: LanguageProfileResource, params: RequestParams = {}) =>\n    this.http.request<LanguageProfileResource, any>({\n      path: `/api/v3/languageprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LanguageProfile\n   * @name V3LanguageprofileList\n   * @request GET:/api/v3/languageprofile\n   * @deprecated\n   * @secure\n   */\n  v3LanguageprofileList = (params: RequestParams = {}) =>\n    this.http.request<LanguageProfileResource[], any>({\n      path: `/api/v3/languageprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LanguageProfile\n   * @name V3LanguageprofileDelete\n   * @request DELETE:/api/v3/languageprofile/{id}\n   * @deprecated\n   * @secure\n   */\n  v3LanguageprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/languageprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LanguageProfile\n   * @name V3LanguageprofileUpdate\n   * @request PUT:/api/v3/languageprofile/{id}\n   * @deprecated\n   * @secure\n   */\n  v3LanguageprofileUpdate = (id: string, data: LanguageProfileResource, params: RequestParams = {}) =>\n    this.http.request<LanguageProfileResource, any>({\n      path: `/api/v3/languageprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LanguageProfile\n   * @name V3LanguageprofileDetail\n   * @request GET:/api/v3/languageprofile/{id}\n   * @secure\n   */\n  v3LanguageprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<LanguageProfileResource, any>({\n      path: `/api/v3/languageprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LanguageProfileSchema\n   * @name V3LanguageprofileSchemaList\n   * @request GET:/api/v3/languageprofile/schema\n   * @deprecated\n   * @secure\n   */\n  v3LanguageprofileSchemaList = (params: RequestParams = {}) =>\n    this.http.request<LanguageProfileResource, any>({\n      path: `/api/v3/languageprofile/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Localization\n   * @name V3LocalizationList\n   * @request GET:/api/v3/localization\n   * @secure\n   */\n  v3LocalizationList = (params: RequestParams = {}) =>\n    this.http.request<LocalizationResource, any>({\n      path: `/api/v3/localization`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Localization\n   * @name V3LocalizationLanguageList\n   * @request GET:/api/v3/localization/language\n   * @secure\n   */\n  v3LocalizationLanguageList = (params: RequestParams = {}) =>\n    this.http.request<LocalizationLanguageResource, any>({\n      path: `/api/v3/localization/language`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Localization\n   * @name V3LocalizationDetail\n   * @request GET:/api/v3/localization/{id}\n   * @secure\n   */\n  v3LocalizationDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<LocalizationResource, any>({\n      path: `/api/v3/localization/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Log\n   * @name V3LogList\n   * @request GET:/api/v3/log\n   * @secure\n   */\n  v3LogList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      level?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<LogResourcePagingResource, any>({\n      path: `/api/v3/log`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LogFile\n   * @name V3LogFileList\n   * @request GET:/api/v3/log/file\n   * @secure\n   */\n  v3LogFileList = (params: RequestParams = {}) =>\n    this.http.request<LogFileResource[], any>({\n      path: `/api/v3/log/file`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LogFile\n   * @name V3LogFileDetail\n   * @request GET:/api/v3/log/file/{filename}\n   * @secure\n   */\n  v3LogFileDetail = (filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/log/file/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ManualImport\n   * @name V3ManualimportList\n   * @request GET:/api/v3/manualimport\n   * @secure\n   */\n  v3ManualimportList = (\n    query?: {\n      folder?: string;\n      downloadId?: string;\n      /** @format int32 */\n      seriesId?: number;\n      /** @format int32 */\n      seasonNumber?: number;\n      /** @default true */\n      filterExistingFiles?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ManualImportResource[], any>({\n      path: `/api/v3/manualimport`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ManualImport\n   * @name V3ManualimportCreate\n   * @request POST:/api/v3/manualimport\n   * @secure\n   */\n  v3ManualimportCreate = (data: ManualImportReprocessResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/manualimport`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaCover\n   * @name V3MediacoverDetail\n   * @request GET:/api/v3/mediacover/{seriesId}/{filename}\n   * @secure\n   */\n  v3MediacoverDetail = (seriesId: number, filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/mediacover/${seriesId}/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V3ConfigMediamanagementList\n   * @request GET:/api/v3/config/mediamanagement\n   * @secure\n   */\n  v3ConfigMediamanagementList = (params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v3/config/mediamanagement`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V3ConfigMediamanagementUpdate\n   * @request PUT:/api/v3/config/mediamanagement/{id}\n   * @secure\n   */\n  v3ConfigMediamanagementUpdate = (id: string, data: MediaManagementConfigResource, params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v3/config/mediamanagement/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V3ConfigMediamanagementDetail\n   * @request GET:/api/v3/config/mediamanagement/{id}\n   * @secure\n   */\n  v3ConfigMediamanagementDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v3/config/mediamanagement/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataList\n   * @request GET:/api/v3/metadata\n   * @secure\n   */\n  v3MetadataList = (params: RequestParams = {}) =>\n    this.http.request<MetadataResource[], any>({\n      path: `/api/v3/metadata`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataCreate\n   * @request POST:/api/v3/metadata\n   * @secure\n   */\n  v3MetadataCreate = (\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v3/metadata`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataUpdate\n   * @request PUT:/api/v3/metadata/{id}\n   * @secure\n   */\n  v3MetadataUpdate = (\n    id: number,\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v3/metadata/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataDelete\n   * @request DELETE:/api/v3/metadata/{id}\n   * @secure\n   */\n  v3MetadataDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/metadata/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataDetail\n   * @request GET:/api/v3/metadata/{id}\n   * @secure\n   */\n  v3MetadataDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v3/metadata/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataSchemaList\n   * @request GET:/api/v3/metadata/schema\n   * @secure\n   */\n  v3MetadataSchemaList = (params: RequestParams = {}) =>\n    this.http.request<MetadataResource[], any>({\n      path: `/api/v3/metadata/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataTestCreate\n   * @request POST:/api/v3/metadata/test\n   * @secure\n   */\n  v3MetadataTestCreate = (\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/metadata/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataTestallCreate\n   * @request POST:/api/v3/metadata/testall\n   * @secure\n   */\n  v3MetadataTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/metadata/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataActionCreate\n   * @request POST:/api/v3/metadata/action/{name}\n   * @secure\n   */\n  v3MetadataActionCreate = (name: string, data: MetadataResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/metadata/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Missing\n   * @name V3WantedMissingList\n   * @request GET:/api/v3/wanted/missing\n   * @secure\n   */\n  v3WantedMissingList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeImages?: boolean;\n      /** @default true */\n      monitored?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<EpisodeResourcePagingResource, any>({\n      path: `/api/v3/wanted/missing`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Missing\n   * @name V3WantedMissingDetail\n   * @request GET:/api/v3/wanted/missing/{id}\n   * @secure\n   */\n  v3WantedMissingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<EpisodeResource, any>({\n      path: `/api/v3/wanted/missing/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V3ConfigNamingList\n   * @request GET:/api/v3/config/naming\n   * @secure\n   */\n  v3ConfigNamingList = (params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v3/config/naming`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V3ConfigNamingUpdate\n   * @request PUT:/api/v3/config/naming/{id}\n   * @secure\n   */\n  v3ConfigNamingUpdate = (id: string, data: NamingConfigResource, params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v3/config/naming/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V3ConfigNamingDetail\n   * @request GET:/api/v3/config/naming/{id}\n   * @secure\n   */\n  v3ConfigNamingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v3/config/naming/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V3ConfigNamingExamplesList\n   * @request GET:/api/v3/config/naming/examples\n   * @secure\n   */\n  v3ConfigNamingExamplesList = (\n    query?: {\n      renameEpisodes?: boolean;\n      replaceIllegalCharacters?: boolean;\n      /** @format int32 */\n      colonReplacementFormat?: number;\n      customColonReplacementFormat?: string;\n      /** @format int32 */\n      multiEpisodeStyle?: number;\n      standardEpisodeFormat?: string;\n      dailyEpisodeFormat?: string;\n      animeEpisodeFormat?: string;\n      seriesFolderFormat?: string;\n      seasonFolderFormat?: string;\n      specialsFolderFormat?: string;\n      /** @format int32 */\n      id?: number;\n      resourceName?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/config/naming/examples`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationList\n   * @request GET:/api/v3/notification\n   * @secure\n   */\n  v3NotificationList = (params: RequestParams = {}) =>\n    this.http.request<NotificationResource[], any>({\n      path: `/api/v3/notification`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationCreate\n   * @request POST:/api/v3/notification\n   * @secure\n   */\n  v3NotificationCreate = (\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v3/notification`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationUpdate\n   * @request PUT:/api/v3/notification/{id}\n   * @secure\n   */\n  v3NotificationUpdate = (\n    id: number,\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v3/notification/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationDelete\n   * @request DELETE:/api/v3/notification/{id}\n   * @secure\n   */\n  v3NotificationDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/notification/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationDetail\n   * @request GET:/api/v3/notification/{id}\n   * @secure\n   */\n  v3NotificationDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v3/notification/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationSchemaList\n   * @request GET:/api/v3/notification/schema\n   * @secure\n   */\n  v3NotificationSchemaList = (params: RequestParams = {}) =>\n    this.http.request<NotificationResource[], any>({\n      path: `/api/v3/notification/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationTestCreate\n   * @request POST:/api/v3/notification/test\n   * @secure\n   */\n  v3NotificationTestCreate = (\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceTest?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/notification/test`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationTestallCreate\n   * @request POST:/api/v3/notification/testall\n   * @secure\n   */\n  v3NotificationTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/notification/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationActionCreate\n   * @request POST:/api/v3/notification/action/{name}\n   * @secure\n   */\n  v3NotificationActionCreate = (name: string, data: NotificationResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/notification/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Parse\n   * @name V3ParseList\n   * @request GET:/api/v3/parse\n   * @secure\n   */\n  v3ParseList = (\n    query?: {\n      title?: string;\n      path?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ParseResource, any>({\n      path: `/api/v3/parse`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionUpdate\n   * @request PUT:/api/v3/qualitydefinition/{id}\n   * @secure\n   */\n  v3QualitydefinitionUpdate = (id: string, data: QualityDefinitionResource, params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource, any>({\n      path: `/api/v3/qualitydefinition/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionDetail\n   * @request GET:/api/v3/qualitydefinition/{id}\n   * @secure\n   */\n  v3QualitydefinitionDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource, any>({\n      path: `/api/v3/qualitydefinition/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionList\n   * @request GET:/api/v3/qualitydefinition\n   * @secure\n   */\n  v3QualitydefinitionList = (params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource[], any>({\n      path: `/api/v3/qualitydefinition`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionUpdateUpdate\n   * @request PUT:/api/v3/qualitydefinition/update\n   * @secure\n   */\n  v3QualitydefinitionUpdateUpdate = (data: QualityDefinitionResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/qualitydefinition/update`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionLimitsList\n   * @request GET:/api/v3/qualitydefinition/limits\n   * @secure\n   */\n  v3QualitydefinitionLimitsList = (params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionLimitsResource, any>({\n      path: `/api/v3/qualitydefinition/limits`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileCreate\n   * @request POST:/api/v3/qualityprofile\n   * @secure\n   */\n  v3QualityprofileCreate = (data: QualityProfileResource, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v3/qualityprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileList\n   * @request GET:/api/v3/qualityprofile\n   * @secure\n   */\n  v3QualityprofileList = (params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource[], any>({\n      path: `/api/v3/qualityprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileDelete\n   * @request DELETE:/api/v3/qualityprofile/{id}\n   * @secure\n   */\n  v3QualityprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/qualityprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileUpdate\n   * @request PUT:/api/v3/qualityprofile/{id}\n   * @secure\n   */\n  v3QualityprofileUpdate = (id: string, data: QualityProfileResource, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v3/qualityprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileDetail\n   * @request GET:/api/v3/qualityprofile/{id}\n   * @secure\n   */\n  v3QualityprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v3/qualityprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfileSchema\n   * @name V3QualityprofileSchemaList\n   * @request GET:/api/v3/qualityprofile/schema\n   * @secure\n   */\n  v3QualityprofileSchemaList = (params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v3/qualityprofile/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V3QueueDelete\n   * @request DELETE:/api/v3/queue/{id}\n   * @secure\n   */\n  v3QueueDelete = (\n    id: number,\n    query?: {\n      /** @default true */\n      removeFromClient?: boolean;\n      /** @default false */\n      blocklist?: boolean;\n      /** @default false */\n      skipRedownload?: boolean;\n      /** @default false */\n      changeCategory?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/queue/${id}`,\n      method: \"DELETE\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V3QueueBulkDelete\n   * @request DELETE:/api/v3/queue/bulk\n   * @secure\n   */\n  v3QueueBulkDelete = (\n    data: QueueBulkResource,\n    query?: {\n      /** @default true */\n      removeFromClient?: boolean;\n      /** @default false */\n      blocklist?: boolean;\n      /** @default false */\n      skipRedownload?: boolean;\n      /** @default false */\n      changeCategory?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/queue/bulk`,\n      method: \"DELETE\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V3QueueList\n   * @request GET:/api/v3/queue\n   * @secure\n   */\n  v3QueueList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeUnknownSeriesItems?: boolean;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisode?: boolean;\n      seriesIds?: number[];\n      protocol?: DownloadProtocol;\n      languages?: number[];\n      quality?: number[];\n      status?: QueueStatus[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<QueueResourcePagingResource, any>({\n      path: `/api/v3/queue`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueAction\n   * @name V3QueueGrabCreate\n   * @request POST:/api/v3/queue/grab/{id}\n   * @secure\n   */\n  v3QueueGrabCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/queue/grab/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueAction\n   * @name V3QueueGrabBulkCreate\n   * @request POST:/api/v3/queue/grab/bulk\n   * @secure\n   */\n  v3QueueGrabBulkCreate = (data: QueueBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/queue/grab/bulk`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueDetails\n   * @name V3QueueDetailsList\n   * @request GET:/api/v3/queue/details\n   * @secure\n   */\n  v3QueueDetailsList = (\n    query?: {\n      /** @format int32 */\n      seriesId?: number;\n      episodeIds?: number[];\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisode?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<QueueResource[], any>({\n      path: `/api/v3/queue/details`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueStatus\n   * @name V3QueueStatusList\n   * @request GET:/api/v3/queue/status\n   * @secure\n   */\n  v3QueueStatusList = (params: RequestParams = {}) =>\n    this.http.request<QueueStatusResource, any>({\n      path: `/api/v3/queue/status`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Release\n   * @name V3ReleaseCreate\n   * @request POST:/api/v3/release\n   * @secure\n   */\n  v3ReleaseCreate = (data: ReleaseResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/release`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Release\n   * @name V3ReleaseList\n   * @request GET:/api/v3/release\n   * @secure\n   */\n  v3ReleaseList = (\n    query?: {\n      /** @format int32 */\n      seriesId?: number;\n      /** @format int32 */\n      episodeId?: number;\n      /** @format int32 */\n      seasonNumber?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ReleaseResource[], any>({\n      path: `/api/v3/release`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileCreate\n   * @request POST:/api/v3/releaseprofile\n   * @secure\n   */\n  v3ReleaseprofileCreate = (data: ReleaseProfileResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v3/releaseprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileList\n   * @request GET:/api/v3/releaseprofile\n   * @secure\n   */\n  v3ReleaseprofileList = (params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource[], any>({\n      path: `/api/v3/releaseprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileDelete\n   * @request DELETE:/api/v3/releaseprofile/{id}\n   * @secure\n   */\n  v3ReleaseprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/releaseprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileUpdate\n   * @request PUT:/api/v3/releaseprofile/{id}\n   * @secure\n   */\n  v3ReleaseprofileUpdate = (id: string, data: ReleaseProfileResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v3/releaseprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileDetail\n   * @request GET:/api/v3/releaseprofile/{id}\n   * @secure\n   */\n  v3ReleaseprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v3/releaseprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleasePush\n   * @name V3ReleasePushCreate\n   * @request POST:/api/v3/release/push\n   * @secure\n   */\n  v3ReleasePushCreate = (data: ReleaseResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseResource[], any>({\n      path: `/api/v3/release/push`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingCreate\n   * @request POST:/api/v3/remotepathmapping\n   * @secure\n   */\n  v3RemotepathmappingCreate = (data: RemotePathMappingResource, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v3/remotepathmapping`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingList\n   * @request GET:/api/v3/remotepathmapping\n   * @secure\n   */\n  v3RemotepathmappingList = (params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource[], any>({\n      path: `/api/v3/remotepathmapping`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingDelete\n   * @request DELETE:/api/v3/remotepathmapping/{id}\n   * @secure\n   */\n  v3RemotepathmappingDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/remotepathmapping/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingUpdate\n   * @request PUT:/api/v3/remotepathmapping/{id}\n   * @secure\n   */\n  v3RemotepathmappingUpdate = (id: string, data: RemotePathMappingResource, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v3/remotepathmapping/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingDetail\n   * @request GET:/api/v3/remotepathmapping/{id}\n   * @secure\n   */\n  v3RemotepathmappingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v3/remotepathmapping/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RenameEpisode\n   * @name V3RenameList\n   * @request GET:/api/v3/rename\n   * @secure\n   */\n  v3RenameList = (\n    query?: {\n      /** @format int32 */\n      seriesId?: number;\n      /** @format int32 */\n      seasonNumber?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<RenameEpisodeResource[], any>({\n      path: `/api/v3/rename`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V3RootfolderCreate\n   * @request POST:/api/v3/rootfolder\n   * @secure\n   */\n  v3RootfolderCreate = (data: RootFolderResource, params: RequestParams = {}) =>\n    this.http.request<RootFolderResource, any>({\n      path: `/api/v3/rootfolder`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V3RootfolderList\n   * @request GET:/api/v3/rootfolder\n   * @secure\n   */\n  v3RootfolderList = (params: RequestParams = {}) =>\n    this.http.request<RootFolderResource[], any>({\n      path: `/api/v3/rootfolder`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V3RootfolderDelete\n   * @request DELETE:/api/v3/rootfolder/{id}\n   * @secure\n   */\n  v3RootfolderDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/rootfolder/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V3RootfolderDetail\n   * @request GET:/api/v3/rootfolder/{id}\n   * @secure\n   */\n  v3RootfolderDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<RootFolderResource, any>({\n      path: `/api/v3/rootfolder/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags SeasonPass\n   * @name V3SeasonpassCreate\n   * @request POST:/api/v3/seasonpass\n   * @secure\n   */\n  v3SeasonpassCreate = (data: SeasonPassResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/seasonpass`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Series\n   * @name V3SeriesList\n   * @request GET:/api/v3/series\n   * @secure\n   */\n  v3SeriesList = (\n    query?: {\n      /** @format int32 */\n      tvdbId?: number;\n      /** @default false */\n      includeSeasonImages?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<SeriesResource[], any>({\n      path: `/api/v3/series`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Series\n   * @name V3SeriesCreate\n   * @request POST:/api/v3/series\n   * @secure\n   */\n  v3SeriesCreate = (data: SeriesResource, params: RequestParams = {}) =>\n    this.http.request<SeriesResource, any>({\n      path: `/api/v3/series`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Series\n   * @name V3SeriesDetail\n   * @request GET:/api/v3/series/{id}\n   * @secure\n   */\n  v3SeriesDetail = (\n    id: number,\n    query?: {\n      /** @default false */\n      includeSeasonImages?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<SeriesResource, any>({\n      path: `/api/v3/series/${id}`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Series\n   * @name V3SeriesUpdate\n   * @request PUT:/api/v3/series/{id}\n   * @secure\n   */\n  v3SeriesUpdate = (\n    id: string,\n    data: SeriesResource,\n    query?: {\n      /** @default false */\n      moveFiles?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<SeriesResource, any>({\n      path: `/api/v3/series/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Series\n   * @name V3SeriesDelete\n   * @request DELETE:/api/v3/series/{id}\n   * @secure\n   */\n  v3SeriesDelete = (\n    id: number,\n    query?: {\n      /** @default false */\n      deleteFiles?: boolean;\n      /** @default false */\n      addImportListExclusion?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/series/${id}`,\n      method: \"DELETE\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags SeriesEditor\n   * @name V3SeriesEditorUpdate\n   * @request PUT:/api/v3/series/editor\n   * @secure\n   */\n  v3SeriesEditorUpdate = (data: SeriesEditorResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/series/editor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags SeriesEditor\n   * @name V3SeriesEditorDelete\n   * @request DELETE:/api/v3/series/editor\n   * @secure\n   */\n  v3SeriesEditorDelete = (data: SeriesEditorResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/series/editor`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags SeriesFolder\n   * @name V3SeriesFolderList\n   * @request GET:/api/v3/series/{id}/folder\n   * @secure\n   */\n  v3SeriesFolderList = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/series/${id}/folder`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags SeriesImport\n   * @name V3SeriesImportCreate\n   * @request POST:/api/v3/series/import\n   * @secure\n   */\n  v3SeriesImportCreate = (data: SeriesResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/series/import`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags SeriesLookup\n   * @name V3SeriesLookupList\n   * @request GET:/api/v3/series/lookup\n   * @secure\n   */\n  v3SeriesLookupList = (\n    query?: {\n      term?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<SeriesResource[], any>({\n      path: `/api/v3/series/lookup`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemStatusList\n   * @request GET:/api/v3/system/status\n   * @secure\n   */\n  v3SystemStatusList = (params: RequestParams = {}) =>\n    this.http.request<SystemResource, any>({\n      path: `/api/v3/system/status`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemRoutesList\n   * @request GET:/api/v3/system/routes\n   * @secure\n   */\n  v3SystemRoutesList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/routes`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemRoutesDuplicateList\n   * @request GET:/api/v3/system/routes/duplicate\n   * @secure\n   */\n  v3SystemRoutesDuplicateList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/routes/duplicate`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemShutdownCreate\n   * @request POST:/api/v3/system/shutdown\n   * @secure\n   */\n  v3SystemShutdownCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/shutdown`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemRestartCreate\n   * @request POST:/api/v3/system/restart\n   * @secure\n   */\n  v3SystemRestartCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/restart`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagList\n   * @request GET:/api/v3/tag\n   * @secure\n   */\n  v3TagList = (params: RequestParams = {}) =>\n    this.http.request<TagResource[], any>({\n      path: `/api/v3/tag`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagCreate\n   * @request POST:/api/v3/tag\n   * @secure\n   */\n  v3TagCreate = (data: TagResource, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v3/tag`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagUpdate\n   * @request PUT:/api/v3/tag/{id}\n   * @secure\n   */\n  v3TagUpdate = (id: string, data: TagResource, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v3/tag/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagDelete\n   * @request DELETE:/api/v3/tag/{id}\n   * @secure\n   */\n  v3TagDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/tag/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagDetail\n   * @request GET:/api/v3/tag/{id}\n   * @secure\n   */\n  v3TagDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v3/tag/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TagDetails\n   * @name V3TagDetailList\n   * @request GET:/api/v3/tag/detail\n   * @secure\n   */\n  v3TagDetailList = (params: RequestParams = {}) =>\n    this.http.request<TagDetailsResource[], any>({\n      path: `/api/v3/tag/detail`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TagDetails\n   * @name V3TagDetailDetail\n   * @request GET:/api/v3/tag/detail/{id}\n   * @secure\n   */\n  v3TagDetailDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TagDetailsResource, any>({\n      path: `/api/v3/tag/detail/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Task\n   * @name V3SystemTaskList\n   * @request GET:/api/v3/system/task\n   * @secure\n   */\n  v3SystemTaskList = (params: RequestParams = {}) =>\n    this.http.request<TaskResource[], any>({\n      path: `/api/v3/system/task`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Task\n   * @name V3SystemTaskDetail\n   * @request GET:/api/v3/system/task/{id}\n   * @secure\n   */\n  v3SystemTaskDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TaskResource, any>({\n      path: `/api/v3/system/task/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V3ConfigUiUpdate\n   * @request PUT:/api/v3/config/ui/{id}\n   * @secure\n   */\n  v3ConfigUiUpdate = (id: string, data: UiConfigResource, params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v3/config/ui/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V3ConfigUiDetail\n   * @request GET:/api/v3/config/ui/{id}\n   * @secure\n   */\n  v3ConfigUiDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v3/config/ui/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V3ConfigUiList\n   * @request GET:/api/v3/config/ui\n   * @secure\n   */\n  v3ConfigUiList = (params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v3/config/ui`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Update\n   * @name V3UpdateList\n   * @request GET:/api/v3/update\n   * @secure\n   */\n  v3UpdateList = (params: RequestParams = {}) =>\n    this.http.request<UpdateResource[], any>({\n      path: `/api/v3/update`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UpdateLogFile\n   * @name V3LogFileUpdateList\n   * @request GET:/api/v3/log/file/update\n   * @secure\n   */\n  v3LogFileUpdateList = (params: RequestParams = {}) =>\n    this.http.request<LogFileResource[], any>({\n      path: `/api/v3/log/file/update`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UpdateLogFile\n   * @name V3LogFileUpdateDetail\n   * @request GET:/api/v3/log/file/update/{filename}\n   * @secure\n   */\n  v3LogFileUpdateDetail = (filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/log/file/update/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/sonarr/Content.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Content<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name ContentDetail\n   * @request GET:/content/{path}\n   * @secure\n   */\n  contentDetail = (path: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/content/${path}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/sonarr/Feed.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Feed<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags CalendarFeed\n   * @name V3CalendarSonarrIcsList\n   * @request GET:/feed/v3/calendar/sonarr.ics\n   * @secure\n   */\n  v3CalendarSonarrIcsList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 7\n       */\n      pastDays?: number;\n      /**\n       * @format int32\n       * @default 28\n       */\n      futureDays?: number;\n      /** @default \"\" */\n      tags?: string;\n      /** @default false */\n      unmonitored?: boolean;\n      /** @default false */\n      premieresOnly?: boolean;\n      /** @default false */\n      asAllDay?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/feed/v3/calendar/sonarr.ics`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/sonarr/Login.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { ContentType, HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Login<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Authentication\n   * @name LoginCreate\n   * @request POST:/login\n   * @secure\n   */\n  loginCreate = (\n    data: {\n      username?: string;\n      password?: string;\n      rememberMe?: string;\n    },\n    query?: {\n      returnUrl?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/login`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.FormData,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name LoginList\n   * @request GET:/login\n   * @secure\n   */\n  loginList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/login`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/sonarr/Logout.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Logout<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Authentication\n   * @name LogoutList\n   * @request GET:/logout\n   * @secure\n   */\n  logoutList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/logout`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/sonarr/Path.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Path<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name GetPath\n   * @request GET:/{path}\n   * @secure\n   */\n  getPath = (path: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/${path}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/sonarr/Ping.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\nimport { PingResource } from \"./data-contracts\";\n\nexport class Ping<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Ping\n   * @name PingList\n   * @request GET:/ping\n   * @secure\n   */\n  pingList = (params: RequestParams = {}) =>\n    this.http.request<PingResource, any>({\n      path: `/ping`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Ping\n   * @name HeadPing\n   * @request HEAD:/ping\n   * @secure\n   */\n  headPing = (params: RequestParams = {}) =>\n    this.http.request<PingResource, any>({\n      path: `/ping`,\n      method: \"HEAD\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/sonarr/data-contracts.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nexport enum UpdateMechanism {\n  BuiltIn = \"builtIn\",\n  Script = \"script\",\n  External = \"external\",\n  Apt = \"apt\",\n  Docker = \"docker\",\n}\n\nexport enum TrackedDownloadStatus {\n  Ok = \"ok\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum TrackedDownloadState {\n  Downloading = \"downloading\",\n  ImportBlocked = \"importBlocked\",\n  ImportPending = \"importPending\",\n  Importing = \"importing\",\n  Imported = \"imported\",\n  FailedPending = \"failedPending\",\n  Failed = \"failed\",\n  Ignored = \"ignored\",\n}\n\nexport enum SortDirection {\n  Default = \"default\",\n  Ascending = \"ascending\",\n  Descending = \"descending\",\n}\n\nexport enum SeriesTypes {\n  Standard = \"standard\",\n  Daily = \"daily\",\n  Anime = \"anime\",\n}\n\nexport enum SeriesStatusType {\n  Continuing = \"continuing\",\n  Ended = \"ended\",\n  Upcoming = \"upcoming\",\n  Deleted = \"deleted\",\n}\n\nexport enum RuntimeMode {\n  Console = \"console\",\n  Service = \"service\",\n  Tray = \"tray\",\n}\n\nexport enum RescanAfterRefreshType {\n  Always = \"always\",\n  AfterManual = \"afterManual\",\n  Never = \"never\",\n}\n\nexport enum ReleaseType {\n  Unknown = \"unknown\",\n  SingleEpisode = \"singleEpisode\",\n  MultiEpisode = \"multiEpisode\",\n  SeasonPack = \"seasonPack\",\n}\n\nexport enum RejectionType {\n  Permanent = \"permanent\",\n  Temporary = \"temporary\",\n}\n\nexport enum QueueStatus {\n  Unknown = \"unknown\",\n  Queued = \"queued\",\n  Paused = \"paused\",\n  Downloading = \"downloading\",\n  Completed = \"completed\",\n  Failed = \"failed\",\n  Warning = \"warning\",\n  Delay = \"delay\",\n  DownloadClientUnavailable = \"downloadClientUnavailable\",\n  Fallback = \"fallback\",\n}\n\nexport enum QualitySource {\n  Unknown = \"unknown\",\n  Television = \"television\",\n  TelevisionRaw = \"televisionRaw\",\n  Web = \"web\",\n  WebRip = \"webRip\",\n  Dvd = \"dvd\",\n  Bluray = \"bluray\",\n  BlurayRaw = \"blurayRaw\",\n}\n\nexport enum ProxyType {\n  Http = \"http\",\n  Socks4 = \"socks4\",\n  Socks5 = \"socks5\",\n}\n\nexport enum ProviderMessageType {\n  Info = \"info\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum ProperDownloadTypes {\n  PreferAndUpgrade = \"preferAndUpgrade\",\n  DoNotUpgrade = \"doNotUpgrade\",\n  DoNotPrefer = \"doNotPrefer\",\n}\n\nexport enum PrivacyLevel {\n  Normal = \"normal\",\n  Password = \"password\",\n  ApiKey = \"apiKey\",\n  UserName = \"userName\",\n}\n\nexport enum NewItemMonitorTypes {\n  All = \"all\",\n  None = \"none\",\n}\n\nexport enum MonitorTypes {\n  Unknown = \"unknown\",\n  All = \"all\",\n  Future = \"future\",\n  Missing = \"missing\",\n  Existing = \"existing\",\n  FirstSeason = \"firstSeason\",\n  LastSeason = \"lastSeason\",\n  LatestSeason = \"latestSeason\",\n  Pilot = \"pilot\",\n  Recent = \"recent\",\n  MonitorSpecials = \"monitorSpecials\",\n  UnmonitorSpecials = \"unmonitorSpecials\",\n  None = \"none\",\n  Skip = \"skip\",\n}\n\nexport enum MediaCoverTypes {\n  Unknown = \"unknown\",\n  Poster = \"poster\",\n  Banner = \"banner\",\n  Fanart = \"fanart\",\n  Screenshot = \"screenshot\",\n  Headshot = \"headshot\",\n  Clearlogo = \"clearlogo\",\n}\n\nexport enum ListSyncLevelType {\n  Disabled = \"disabled\",\n  LogOnly = \"logOnly\",\n  KeepAndUnmonitor = \"keepAndUnmonitor\",\n  KeepAndTag = \"keepAndTag\",\n}\n\nexport enum ImportListType {\n  Program = \"program\",\n  Plex = \"plex\",\n  Trakt = \"trakt\",\n  Simkl = \"simkl\",\n  Other = \"other\",\n  Advanced = \"advanced\",\n}\n\nexport enum HealthCheckResult {\n  Ok = \"ok\",\n  Notice = \"notice\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum FileDateType {\n  None = \"none\",\n  LocalAirDate = \"localAirDate\",\n  UtcAirDate = \"utcAirDate\",\n}\n\nexport enum EpisodeTitleRequiredType {\n  Always = \"always\",\n  BulkSeasonReleases = \"bulkSeasonReleases\",\n  Never = \"never\",\n}\n\nexport enum EpisodeHistoryEventType {\n  Unknown = \"unknown\",\n  Grabbed = \"grabbed\",\n  SeriesFolderImported = \"seriesFolderImported\",\n  DownloadFolderImported = \"downloadFolderImported\",\n  DownloadFailed = \"downloadFailed\",\n  EpisodeFileDeleted = \"episodeFileDeleted\",\n  EpisodeFileRenamed = \"episodeFileRenamed\",\n  DownloadIgnored = \"downloadIgnored\",\n}\n\nexport enum DownloadProtocol {\n  Unknown = \"unknown\",\n  Usenet = \"usenet\",\n  Torrent = \"torrent\",\n}\n\nexport enum DatabaseType {\n  SqLite = \"sqLite\",\n  PostgreSQL = \"postgreSQL\",\n}\n\nexport enum CommandTrigger {\n  Unspecified = \"unspecified\",\n  Manual = \"manual\",\n  Scheduled = \"scheduled\",\n}\n\nexport enum CommandStatus {\n  Queued = \"queued\",\n  Started = \"started\",\n  Completed = \"completed\",\n  Failed = \"failed\",\n  Aborted = \"aborted\",\n  Cancelled = \"cancelled\",\n  Orphaned = \"orphaned\",\n}\n\nexport enum CommandResult {\n  Unknown = \"unknown\",\n  Successful = \"successful\",\n  Unsuccessful = \"unsuccessful\",\n}\n\nexport enum CommandPriority {\n  Normal = \"normal\",\n  High = \"high\",\n  Low = \"low\",\n}\n\nexport enum CertificateValidationType {\n  Enabled = \"enabled\",\n  DisabledForLocalAddresses = \"disabledForLocalAddresses\",\n  Disabled = \"disabled\",\n}\n\nexport enum BackupType {\n  Scheduled = \"scheduled\",\n  Manual = \"manual\",\n  Update = \"update\",\n}\n\nexport enum AuthenticationType {\n  None = \"none\",\n  Basic = \"basic\",\n  Forms = \"forms\",\n  External = \"external\",\n}\n\nexport enum AuthenticationRequiredType {\n  Enabled = \"enabled\",\n  DisabledForLocalAddresses = \"disabledForLocalAddresses\",\n}\n\nexport enum ApplyTags {\n  Add = \"add\",\n  Remove = \"remove\",\n  Replace = \"replace\",\n}\n\nexport interface AddSeriesOptions {\n  ignoreEpisodesWithFiles?: boolean;\n  ignoreEpisodesWithoutFiles?: boolean;\n  monitor?: MonitorTypes;\n  searchForMissingEpisodes?: boolean;\n  searchForCutoffUnmetEpisodes?: boolean;\n}\n\nexport interface AlternateTitleResource {\n  title?: string | null;\n  /** @format int32 */\n  seasonNumber?: number | null;\n  /** @format int32 */\n  sceneSeasonNumber?: number | null;\n  sceneOrigin?: string | null;\n  comment?: string | null;\n}\n\nexport interface AutoTaggingResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  removeTagsAutomatically?: boolean;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  specifications?: AutoTaggingSpecificationSchema[] | null;\n}\n\nexport interface AutoTaggingSpecificationSchema {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  implementation?: string | null;\n  implementationName?: string | null;\n  negate?: boolean;\n  required?: boolean;\n  fields?: Field[] | null;\n}\n\nexport interface BackupResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  path?: string | null;\n  type?: BackupType;\n  /** @format int64 */\n  size?: number;\n  /** @format date-time */\n  time?: string;\n}\n\nexport interface BlocklistBulkResource {\n  ids?: number[] | null;\n}\n\nexport interface BlocklistResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  seriesId?: number;\n  episodeIds?: number[] | null;\n  sourceTitle?: string | null;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format date-time */\n  date?: string;\n  protocol?: DownloadProtocol;\n  indexer?: string | null;\n  message?: string | null;\n  series?: SeriesResource;\n}\n\nexport interface BlocklistResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: BlocklistResource[] | null;\n}\n\nexport interface Command {\n  sendUpdatesToClient?: boolean;\n  updateScheduledTask?: boolean;\n  completionMessage?: string | null;\n  requiresDiskAccess?: boolean;\n  isExclusive?: boolean;\n  isLongRunning?: boolean;\n  name?: string | null;\n  /** @format date-time */\n  lastExecutionTime?: string | null;\n  /** @format date-time */\n  lastStartTime?: string | null;\n  trigger?: CommandTrigger;\n  suppressMessages?: boolean;\n  clientUserAgent?: string | null;\n}\n\nexport interface CommandResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  commandName?: string | null;\n  message?: string | null;\n  body?: Command;\n  priority?: CommandPriority;\n  status?: CommandStatus;\n  result?: CommandResult;\n  /** @format date-time */\n  queued?: string;\n  /** @format date-time */\n  started?: string | null;\n  /** @format date-time */\n  ended?: string | null;\n  /** @format date-span */\n  duration?: string | null;\n  exception?: string | null;\n  trigger?: CommandTrigger;\n  clientUserAgent?: string | null;\n  /** @format date-time */\n  stateChangeTime?: string | null;\n  sendUpdatesToClient?: boolean;\n  updateScheduledTask?: boolean;\n  /** @format date-time */\n  lastExecutionTime?: string | null;\n}\n\nexport interface CustomFilterResource {\n  /** @format int32 */\n  id?: number;\n  type?: string | null;\n  label?: string | null;\n  filters?: Record<string, any>[] | null;\n}\n\nexport interface CustomFormatBulkResource {\n  /** @uniqueItems true */\n  ids?: number[] | null;\n  includeCustomFormatWhenRenaming?: boolean | null;\n}\n\nexport interface CustomFormatResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  includeCustomFormatWhenRenaming?: boolean | null;\n  specifications?: CustomFormatSpecificationSchema[] | null;\n}\n\nexport interface CustomFormatSpecificationSchema {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  implementation?: string | null;\n  implementationName?: string | null;\n  infoLink?: string | null;\n  negate?: boolean;\n  required?: boolean;\n  fields?: Field[] | null;\n  presets?: CustomFormatSpecificationSchema[] | null;\n}\n\nexport interface DelayProfileResource {\n  /** @format int32 */\n  id?: number;\n  enableUsenet?: boolean;\n  enableTorrent?: boolean;\n  preferredProtocol?: DownloadProtocol;\n  /** @format int32 */\n  usenetDelay?: number;\n  /** @format int32 */\n  torrentDelay?: number;\n  bypassIfHighestQuality?: boolean;\n  bypassIfAboveCustomFormatScore?: boolean;\n  /** @format int32 */\n  minimumCustomFormatScore?: number;\n  /** @format int32 */\n  order?: number;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n}\n\nexport interface DiskSpaceResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  label?: string | null;\n  /** @format int64 */\n  freeSpace?: number;\n  /** @format int64 */\n  totalSpace?: number;\n}\n\nexport interface DownloadClientBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enable?: boolean | null;\n  /** @format int32 */\n  priority?: number | null;\n  removeCompletedDownloads?: boolean | null;\n  removeFailedDownloads?: boolean | null;\n}\n\nexport interface DownloadClientConfigResource {\n  /** @format int32 */\n  id?: number;\n  downloadClientWorkingFolders?: string | null;\n  enableCompletedDownloadHandling?: boolean;\n  autoRedownloadFailed?: boolean;\n  autoRedownloadFailedFromInteractiveSearch?: boolean;\n}\n\nexport interface DownloadClientResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: DownloadClientResource[] | null;\n  enable?: boolean;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  priority?: number;\n  removeCompletedDownloads?: boolean;\n  removeFailedDownloads?: boolean;\n}\n\nexport interface EpisodeFileListResource {\n  episodeFileIds?: number[] | null;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  sceneName?: string | null;\n  releaseGroup?: string | null;\n}\n\nexport interface EpisodeFileResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  seriesId?: number;\n  /** @format int32 */\n  seasonNumber?: number;\n  relativePath?: string | null;\n  path?: string | null;\n  /** @format int64 */\n  size?: number;\n  /** @format date-time */\n  dateAdded?: string;\n  sceneName?: string | null;\n  releaseGroup?: string | null;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  /** @format int32 */\n  indexerFlags?: number | null;\n  releaseType?: ReleaseType;\n  mediaInfo?: MediaInfoResource;\n  qualityCutoffNotMet?: boolean;\n}\n\nexport interface EpisodeResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  seriesId?: number;\n  /** @format int32 */\n  tvdbId?: number;\n  /** @format int32 */\n  episodeFileId?: number;\n  /** @format int32 */\n  seasonNumber?: number;\n  /** @format int32 */\n  episodeNumber?: number;\n  title?: string | null;\n  airDate?: string | null;\n  /** @format date-time */\n  airDateUtc?: string | null;\n  /** @format date-time */\n  lastSearchTime?: string | null;\n  /** @format int32 */\n  runtime?: number;\n  finaleType?: string | null;\n  overview?: string | null;\n  episodeFile?: EpisodeFileResource;\n  hasFile?: boolean;\n  monitored?: boolean;\n  /** @format int32 */\n  absoluteEpisodeNumber?: number | null;\n  /** @format int32 */\n  sceneAbsoluteEpisodeNumber?: number | null;\n  /** @format int32 */\n  sceneEpisodeNumber?: number | null;\n  /** @format int32 */\n  sceneSeasonNumber?: number | null;\n  unverifiedSceneNumbering?: boolean;\n  /** @format date-time */\n  endTime?: string | null;\n  /** @format date-time */\n  grabDate?: string | null;\n  series?: SeriesResource;\n  images?: MediaCover[] | null;\n}\n\nexport interface EpisodeResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: EpisodeResource[] | null;\n}\n\nexport interface EpisodesMonitoredResource {\n  episodeIds?: number[] | null;\n  monitored?: boolean;\n}\n\nexport interface Field {\n  /** @format int32 */\n  order?: number;\n  name?: string | null;\n  label?: string | null;\n  unit?: string | null;\n  helpText?: string | null;\n  helpTextWarning?: string | null;\n  helpLink?: string | null;\n  value?: any;\n  type?: string | null;\n  advanced?: boolean;\n  selectOptions?: SelectOption[] | null;\n  selectOptionsProviderAction?: string | null;\n  section?: string | null;\n  hidden?: string | null;\n  privacy?: PrivacyLevel;\n  placeholder?: string | null;\n  isFloat?: boolean;\n}\n\nexport interface HealthResource {\n  /** @format int32 */\n  id?: number;\n  source?: string | null;\n  type?: HealthCheckResult;\n  message?: string | null;\n  wikiUrl?: HttpUri;\n}\n\nexport interface HistoryResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  episodeId?: number;\n  /** @format int32 */\n  seriesId?: number;\n  sourceTitle?: string | null;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  qualityCutoffNotMet?: boolean;\n  /** @format date-time */\n  date?: string;\n  downloadId?: string | null;\n  eventType?: EpisodeHistoryEventType;\n  data?: Record<string, string | null>;\n  episode?: EpisodeResource;\n  series?: SeriesResource;\n}\n\nexport interface HistoryResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: HistoryResource[] | null;\n}\n\nexport interface HostConfigResource {\n  /** @format int32 */\n  id?: number;\n  bindAddress?: string | null;\n  /** @format int32 */\n  port?: number;\n  /** @format int32 */\n  sslPort?: number;\n  enableSsl?: boolean;\n  launchBrowser?: boolean;\n  authenticationMethod?: AuthenticationType;\n  authenticationRequired?: AuthenticationRequiredType;\n  analyticsEnabled?: boolean;\n  username?: string | null;\n  password?: string | null;\n  passwordConfirmation?: string | null;\n  logLevel?: string | null;\n  /** @format int32 */\n  logSizeLimit?: number;\n  consoleLogLevel?: string | null;\n  branch?: string | null;\n  apiKey?: string | null;\n  sslCertPath?: string | null;\n  sslCertPassword?: string | null;\n  urlBase?: string | null;\n  instanceName?: string | null;\n  applicationUrl?: string | null;\n  updateAutomatically?: boolean;\n  updateMechanism?: UpdateMechanism;\n  updateScriptPath?: string | null;\n  proxyEnabled?: boolean;\n  proxyType?: ProxyType;\n  proxyHostname?: string | null;\n  /** @format int32 */\n  proxyPort?: number;\n  proxyUsername?: string | null;\n  proxyPassword?: string | null;\n  proxyBypassFilter?: string | null;\n  proxyBypassLocalAddresses?: boolean;\n  certificateValidation?: CertificateValidationType;\n  backupFolder?: string | null;\n  /** @format int32 */\n  backupInterval?: number;\n  /** @format int32 */\n  backupRetention?: number;\n  trustCgnatIpAddresses?: boolean;\n}\n\nexport interface HttpUri {\n  fullUri?: string | null;\n  scheme?: string | null;\n  host?: string | null;\n  /** @format int32 */\n  port?: number | null;\n  path?: string | null;\n  query?: string | null;\n  fragment?: string | null;\n}\n\nexport interface ImportListBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enableAutomaticAdd?: boolean | null;\n  rootFolderPath?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number | null;\n}\n\nexport interface ImportListConfigResource {\n  /** @format int32 */\n  id?: number;\n  listSyncLevel?: ListSyncLevelType;\n  /** @format int32 */\n  listSyncTag?: number;\n}\n\nexport interface ImportListExclusionBulkResource {\n  /** @uniqueItems true */\n  ids?: number[] | null;\n}\n\nexport interface ImportListExclusionResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  tvdbId?: number;\n  title?: string | null;\n}\n\nexport interface ImportListExclusionResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: ImportListExclusionResource[] | null;\n}\n\nexport interface ImportListResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: ImportListResource[] | null;\n  enableAutomaticAdd?: boolean;\n  searchForMissingEpisodes?: boolean;\n  shouldMonitor?: MonitorTypes;\n  monitorNewItems?: NewItemMonitorTypes;\n  rootFolderPath?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number;\n  seriesType?: SeriesTypes;\n  seasonFolder?: boolean;\n  listType?: ImportListType;\n  /** @format int32 */\n  listOrder?: number;\n  /** @format date-span */\n  minRefreshInterval?: string;\n}\n\nexport interface ImportRejectionResource {\n  reason?: string | null;\n  type?: RejectionType;\n}\n\nexport interface IndexerBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enableRss?: boolean | null;\n  enableAutomaticSearch?: boolean | null;\n  enableInteractiveSearch?: boolean | null;\n  /** @format int32 */\n  priority?: number | null;\n}\n\nexport interface IndexerConfigResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  minimumAge?: number;\n  /** @format int32 */\n  retention?: number;\n  /** @format int32 */\n  maximumSize?: number;\n  /** @format int32 */\n  rssSyncInterval?: number;\n}\n\nexport interface IndexerFlagResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  nameLower?: string | null;\n}\n\nexport interface IndexerResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: IndexerResource[] | null;\n  enableRss?: boolean;\n  enableAutomaticSearch?: boolean;\n  enableInteractiveSearch?: boolean;\n  supportsRss?: boolean;\n  supportsSearch?: boolean;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  priority?: number;\n  /** @format int32 */\n  seasonSearchMaximumSingleEpisodeAge?: number;\n  /** @format int32 */\n  downloadClientId?: number;\n}\n\nexport interface Language {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n}\n\nexport interface LanguageProfileItemResource {\n  /** @format int32 */\n  id?: number;\n  language?: Language;\n  allowed?: boolean;\n}\n\nexport interface LanguageProfileResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  upgradeAllowed?: boolean;\n  cutoff?: Language;\n  languages?: LanguageProfileItemResource[] | null;\n}\n\nexport interface LanguageResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  nameLower?: string | null;\n}\n\nexport interface LocalizationLanguageResource {\n  identifier?: string | null;\n}\n\nexport interface LocalizationResource {\n  /** @format int32 */\n  id?: number;\n  strings?: Record<string, string | null>;\n}\n\nexport interface LogFileResource {\n  /** @format int32 */\n  id?: number;\n  filename?: string | null;\n  /** @format date-time */\n  lastWriteTime?: string;\n  contentsUrl?: string | null;\n  downloadUrl?: string | null;\n}\n\nexport interface LogResource {\n  /** @format int32 */\n  id?: number;\n  /** @format date-time */\n  time?: string;\n  exception?: string | null;\n  exceptionType?: string | null;\n  level?: string | null;\n  logger?: string | null;\n  message?: string | null;\n  method?: string | null;\n}\n\nexport interface LogResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: LogResource[] | null;\n}\n\nexport interface ManualImportReprocessResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  /** @format int32 */\n  seriesId?: number;\n  /** @format int32 */\n  seasonNumber?: number | null;\n  episodes?: EpisodeResource[] | null;\n  episodeIds?: number[] | null;\n  quality?: QualityModel;\n  languages?: Language[] | null;\n  releaseGroup?: string | null;\n  downloadId?: string | null;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  /** @format int32 */\n  indexerFlags?: number;\n  releaseType?: ReleaseType;\n  rejections?: ImportRejectionResource[] | null;\n}\n\nexport interface ManualImportResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  relativePath?: string | null;\n  folderName?: string | null;\n  name?: string | null;\n  /** @format int64 */\n  size?: number;\n  series?: SeriesResource;\n  /** @format int32 */\n  seasonNumber?: number | null;\n  episodes?: EpisodeResource[] | null;\n  /** @format int32 */\n  episodeFileId?: number | null;\n  releaseGroup?: string | null;\n  quality?: QualityModel;\n  languages?: Language[] | null;\n  /** @format int32 */\n  qualityWeight?: number;\n  downloadId?: string | null;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  /** @format int32 */\n  indexerFlags?: number;\n  releaseType?: ReleaseType;\n  rejections?: ImportRejectionResource[] | null;\n}\n\nexport interface MediaCover {\n  coverType?: MediaCoverTypes;\n  url?: string | null;\n  remoteUrl?: string | null;\n}\n\nexport interface MediaInfoResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int64 */\n  audioBitrate?: number;\n  /** @format double */\n  audioChannels?: number;\n  audioCodec?: string | null;\n  audioLanguages?: string | null;\n  /** @format int32 */\n  audioStreamCount?: number;\n  /** @format int32 */\n  videoBitDepth?: number;\n  /** @format int64 */\n  videoBitrate?: number;\n  videoCodec?: string | null;\n  /** @format double */\n  videoFps?: number;\n  videoDynamicRange?: string | null;\n  videoDynamicRangeType?: string | null;\n  resolution?: string | null;\n  runTime?: string | null;\n  scanType?: string | null;\n  subtitles?: string | null;\n}\n\nexport interface MediaManagementConfigResource {\n  /** @format int32 */\n  id?: number;\n  autoUnmonitorPreviouslyDownloadedEpisodes?: boolean;\n  recycleBin?: string | null;\n  /** @format int32 */\n  recycleBinCleanupDays?: number;\n  downloadPropersAndRepacks?: ProperDownloadTypes;\n  createEmptySeriesFolders?: boolean;\n  deleteEmptyFolders?: boolean;\n  fileDate?: FileDateType;\n  rescanAfterRefresh?: RescanAfterRefreshType;\n  setPermissionsLinux?: boolean;\n  chmodFolder?: string | null;\n  chownGroup?: string | null;\n  episodeTitleRequired?: EpisodeTitleRequiredType;\n  skipFreeSpaceCheckWhenImporting?: boolean;\n  /** @format int32 */\n  minimumFreeSpaceWhenImporting?: number;\n  copyUsingHardlinks?: boolean;\n  useScriptImport?: boolean;\n  scriptImportPath?: string | null;\n  importExtraFiles?: boolean;\n  extraFileExtensions?: string | null;\n  enableMediaInfo?: boolean;\n}\n\nexport interface MetadataResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: MetadataResource[] | null;\n  enable?: boolean;\n}\n\nexport interface MonitoringOptions {\n  ignoreEpisodesWithFiles?: boolean;\n  ignoreEpisodesWithoutFiles?: boolean;\n  monitor?: MonitorTypes;\n}\n\nexport interface NamingConfigResource {\n  /** @format int32 */\n  id?: number;\n  renameEpisodes?: boolean;\n  replaceIllegalCharacters?: boolean;\n  /** @format int32 */\n  colonReplacementFormat?: number;\n  customColonReplacementFormat?: string | null;\n  /** @format int32 */\n  multiEpisodeStyle?: number;\n  standardEpisodeFormat?: string | null;\n  dailyEpisodeFormat?: string | null;\n  animeEpisodeFormat?: string | null;\n  seriesFolderFormat?: string | null;\n  seasonFolderFormat?: string | null;\n  specialsFolderFormat?: string | null;\n}\n\nexport interface NotificationResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: NotificationResource[] | null;\n  link?: string | null;\n  onGrab?: boolean;\n  onDownload?: boolean;\n  onUpgrade?: boolean;\n  onImportComplete?: boolean;\n  onRename?: boolean;\n  onSeriesAdd?: boolean;\n  onSeriesDelete?: boolean;\n  onEpisodeFileDelete?: boolean;\n  onEpisodeFileDeleteForUpgrade?: boolean;\n  onHealthIssue?: boolean;\n  includeHealthWarnings?: boolean;\n  onHealthRestored?: boolean;\n  onApplicationUpdate?: boolean;\n  onManualInteractionRequired?: boolean;\n  supportsOnGrab?: boolean;\n  supportsOnDownload?: boolean;\n  supportsOnUpgrade?: boolean;\n  supportsOnImportComplete?: boolean;\n  supportsOnRename?: boolean;\n  supportsOnSeriesAdd?: boolean;\n  supportsOnSeriesDelete?: boolean;\n  supportsOnEpisodeFileDelete?: boolean;\n  supportsOnEpisodeFileDeleteForUpgrade?: boolean;\n  supportsOnHealthIssue?: boolean;\n  supportsOnHealthRestored?: boolean;\n  supportsOnApplicationUpdate?: boolean;\n  supportsOnManualInteractionRequired?: boolean;\n  testCommand?: string | null;\n}\n\nexport interface ParseResource {\n  /** @format int32 */\n  id?: number;\n  title?: string | null;\n  parsedEpisodeInfo?: ParsedEpisodeInfo;\n  series?: SeriesResource;\n  episodes?: EpisodeResource[] | null;\n  languages?: Language[] | null;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n}\n\nexport interface ParsedEpisodeInfo {\n  releaseTitle?: string | null;\n  seriesTitle?: string | null;\n  seriesTitleInfo?: SeriesTitleInfo;\n  quality?: QualityModel;\n  /** @format int32 */\n  seasonNumber?: number;\n  episodeNumbers?: number[] | null;\n  absoluteEpisodeNumbers?: number[] | null;\n  specialAbsoluteEpisodeNumbers?: number[] | null;\n  airDate?: string | null;\n  languages?: Language[] | null;\n  fullSeason?: boolean;\n  isPartialSeason?: boolean;\n  isMultiSeason?: boolean;\n  isSeasonExtra?: boolean;\n  isSplitEpisode?: boolean;\n  isMiniSeries?: boolean;\n  special?: boolean;\n  releaseGroup?: string | null;\n  releaseHash?: string | null;\n  /** @format int32 */\n  seasonPart?: number;\n  releaseTokens?: string | null;\n  /** @format int32 */\n  dailyPart?: number | null;\n  isDaily?: boolean;\n  isAbsoluteNumbering?: boolean;\n  isPossibleSpecialEpisode?: boolean;\n  isPossibleSceneSeasonSpecial?: boolean;\n  releaseType?: ReleaseType;\n}\n\nexport interface PingResource {\n  status?: string | null;\n}\n\nexport interface ProfileFormatItemResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  format?: number;\n  name?: string | null;\n  /** @format int32 */\n  score?: number;\n}\n\nexport interface ProviderMessage {\n  message?: string | null;\n  type?: ProviderMessageType;\n}\n\nexport interface Quality {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  source?: QualitySource;\n  /** @format int32 */\n  resolution?: number;\n}\n\nexport interface QualityDefinitionLimitsResource {\n  /** @format int32 */\n  min?: number;\n  /** @format int32 */\n  max?: number;\n}\n\nexport interface QualityDefinitionResource {\n  /** @format int32 */\n  id?: number;\n  quality?: Quality;\n  title?: string | null;\n  /** @format int32 */\n  weight?: number;\n  /** @format double */\n  minSize?: number | null;\n  /** @format double */\n  maxSize?: number | null;\n  /** @format double */\n  preferredSize?: number | null;\n}\n\nexport interface QualityModel {\n  quality?: Quality;\n  revision?: Revision;\n}\n\nexport interface QualityProfileQualityItemResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  quality?: Quality;\n  items?: QualityProfileQualityItemResource[] | null;\n  allowed?: boolean;\n}\n\nexport interface QualityProfileResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  upgradeAllowed?: boolean;\n  /** @format int32 */\n  cutoff?: number;\n  items?: QualityProfileQualityItemResource[] | null;\n  /** @format int32 */\n  minFormatScore?: number;\n  /** @format int32 */\n  cutoffFormatScore?: number;\n  /** @format int32 */\n  minUpgradeFormatScore?: number;\n  formatItems?: ProfileFormatItemResource[] | null;\n}\n\nexport interface QueueBulkResource {\n  ids?: number[] | null;\n}\n\nexport interface QueueResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  seriesId?: number | null;\n  /** @format int32 */\n  episodeId?: number | null;\n  /** @format int32 */\n  seasonNumber?: number | null;\n  series?: SeriesResource;\n  episode?: EpisodeResource;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  /** @format double */\n  size?: number;\n  title?: string | null;\n  /** @format date-time */\n  estimatedCompletionTime?: string | null;\n  /** @format date-time */\n  added?: string | null;\n  status?: QueueStatus;\n  trackedDownloadStatus?: TrackedDownloadStatus;\n  trackedDownloadState?: TrackedDownloadState;\n  statusMessages?: TrackedDownloadStatusMessage[] | null;\n  errorMessage?: string | null;\n  downloadId?: string | null;\n  protocol?: DownloadProtocol;\n  downloadClient?: string | null;\n  downloadClientHasPostImportCategory?: boolean;\n  indexer?: string | null;\n  outputPath?: string | null;\n  episodeHasFile?: boolean;\n  /**\n   * @deprecated\n   * @format double\n   */\n  sizeleft?: number;\n  /**\n   * @deprecated\n   * @format date-span\n   */\n  timeleft?: string | null;\n}\n\nexport interface QueueResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: QueueResource[] | null;\n}\n\nexport interface QueueStatusResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  totalCount?: number;\n  /** @format int32 */\n  count?: number;\n  /** @format int32 */\n  unknownCount?: number;\n  errors?: boolean;\n  warnings?: boolean;\n  unknownErrors?: boolean;\n  unknownWarnings?: boolean;\n}\n\nexport interface Ratings {\n  /** @format int32 */\n  votes?: number;\n  /** @format double */\n  value?: number;\n}\n\nexport interface ReleaseEpisodeResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  seasonNumber?: number;\n  /** @format int32 */\n  episodeNumber?: number;\n  /** @format int32 */\n  absoluteEpisodeNumber?: number | null;\n  title?: string | null;\n}\n\nexport interface ReleaseProfileResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  enabled?: boolean;\n  required?: any;\n  ignored?: any;\n  /** @format int32 */\n  indexerId?: number;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n}\n\nexport interface ReleaseResource {\n  /** @format int32 */\n  id?: number;\n  guid?: string | null;\n  quality?: QualityModel;\n  /** @format int32 */\n  qualityWeight?: number;\n  /** @format int32 */\n  age?: number;\n  /** @format double */\n  ageHours?: number;\n  /** @format double */\n  ageMinutes?: number;\n  /** @format int64 */\n  size?: number;\n  /** @format int32 */\n  indexerId?: number;\n  indexer?: string | null;\n  releaseGroup?: string | null;\n  subGroup?: string | null;\n  releaseHash?: string | null;\n  title?: string | null;\n  fullSeason?: boolean;\n  sceneSource?: boolean;\n  /** @format int32 */\n  seasonNumber?: number;\n  languages?: Language[] | null;\n  /** @format int32 */\n  languageWeight?: number;\n  airDate?: string | null;\n  seriesTitle?: string | null;\n  episodeNumbers?: number[] | null;\n  absoluteEpisodeNumbers?: number[] | null;\n  /** @format int32 */\n  mappedSeasonNumber?: number | null;\n  mappedEpisodeNumbers?: number[] | null;\n  mappedAbsoluteEpisodeNumbers?: number[] | null;\n  /** @format int32 */\n  mappedSeriesId?: number | null;\n  mappedEpisodeInfo?: ReleaseEpisodeResource[] | null;\n  approved?: boolean;\n  temporarilyRejected?: boolean;\n  rejected?: boolean;\n  /** @format int32 */\n  tvdbId?: number;\n  /** @format int32 */\n  tvRageId?: number;\n  imdbId?: string | null;\n  rejections?: string[] | null;\n  /** @format date-time */\n  publishDate?: string;\n  commentUrl?: string | null;\n  downloadUrl?: string | null;\n  infoUrl?: string | null;\n  episodeRequested?: boolean;\n  downloadAllowed?: boolean;\n  /** @format int32 */\n  releaseWeight?: number;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  sceneMapping?: AlternateTitleResource;\n  magnetUrl?: string | null;\n  infoHash?: string | null;\n  /** @format int32 */\n  seeders?: number | null;\n  /** @format int32 */\n  leechers?: number | null;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  indexerFlags?: number;\n  isDaily?: boolean;\n  isAbsoluteNumbering?: boolean;\n  isPossibleSpecialEpisode?: boolean;\n  special?: boolean;\n  /** @format int32 */\n  seriesId?: number | null;\n  /** @format int32 */\n  episodeId?: number | null;\n  episodeIds?: number[] | null;\n  /** @format int32 */\n  downloadClientId?: number | null;\n  downloadClient?: string | null;\n  shouldOverride?: boolean | null;\n}\n\nexport interface RemotePathMappingResource {\n  /** @format int32 */\n  id?: number;\n  host?: string | null;\n  remotePath?: string | null;\n  localPath?: string | null;\n}\n\nexport interface RenameEpisodeResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  seriesId?: number;\n  /** @format int32 */\n  seasonNumber?: number;\n  episodeNumbers?: number[] | null;\n  /** @format int32 */\n  episodeFileId?: number;\n  existingPath?: string | null;\n  newPath?: string | null;\n}\n\nexport interface Revision {\n  /** @format int32 */\n  version?: number;\n  /** @format int32 */\n  real?: number;\n  isRepack?: boolean;\n}\n\nexport interface RootFolderResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  accessible?: boolean;\n  /** @format int64 */\n  freeSpace?: number | null;\n  unmappedFolders?: UnmappedFolder[] | null;\n}\n\nexport interface SeasonPassResource {\n  series?: SeasonPassSeriesResource[] | null;\n  monitoringOptions?: MonitoringOptions;\n}\n\nexport interface SeasonPassSeriesResource {\n  /** @format int32 */\n  id?: number;\n  monitored?: boolean | null;\n  seasons?: SeasonResource[] | null;\n}\n\nexport interface SeasonResource {\n  /** @format int32 */\n  seasonNumber?: number;\n  monitored?: boolean;\n  statistics?: SeasonStatisticsResource;\n  images?: MediaCover[] | null;\n}\n\nexport interface SeasonStatisticsResource {\n  /** @format date-time */\n  nextAiring?: string | null;\n  /** @format date-time */\n  previousAiring?: string | null;\n  /** @format int32 */\n  episodeFileCount?: number;\n  /** @format int32 */\n  episodeCount?: number;\n  /** @format int32 */\n  totalEpisodeCount?: number;\n  /** @format int64 */\n  sizeOnDisk?: number;\n  releaseGroups?: string[] | null;\n  /** @format double */\n  percentOfEpisodes?: number;\n}\n\nexport interface SelectOption {\n  /** @format int32 */\n  value?: number;\n  name?: string | null;\n  /** @format int32 */\n  order?: number;\n  hint?: string | null;\n}\n\nexport interface SeriesEditorResource {\n  seriesIds?: number[] | null;\n  monitored?: boolean | null;\n  monitorNewItems?: NewItemMonitorTypes;\n  /** @format int32 */\n  qualityProfileId?: number | null;\n  seriesType?: SeriesTypes;\n  seasonFolder?: boolean | null;\n  rootFolderPath?: string | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  moveFiles?: boolean;\n  deleteFiles?: boolean;\n  addImportListExclusion?: boolean;\n}\n\nexport interface SeriesResource {\n  /** @format int32 */\n  id?: number;\n  title?: string | null;\n  alternateTitles?: AlternateTitleResource[] | null;\n  sortTitle?: string | null;\n  status?: SeriesStatusType;\n  ended?: boolean;\n  profileName?: string | null;\n  overview?: string | null;\n  /** @format date-time */\n  nextAiring?: string | null;\n  /** @format date-time */\n  previousAiring?: string | null;\n  network?: string | null;\n  airTime?: string | null;\n  images?: MediaCover[] | null;\n  originalLanguage?: Language;\n  remotePoster?: string | null;\n  seasons?: SeasonResource[] | null;\n  /** @format int32 */\n  year?: number;\n  path?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number;\n  seasonFolder?: boolean;\n  monitored?: boolean;\n  monitorNewItems?: NewItemMonitorTypes;\n  useSceneNumbering?: boolean;\n  /** @format int32 */\n  runtime?: number;\n  /** @format int32 */\n  tvdbId?: number;\n  /** @format int32 */\n  tvRageId?: number;\n  /** @format int32 */\n  tvMazeId?: number;\n  /** @format int32 */\n  tmdbId?: number;\n  /** @format date-time */\n  firstAired?: string | null;\n  /** @format date-time */\n  lastAired?: string | null;\n  seriesType?: SeriesTypes;\n  cleanTitle?: string | null;\n  imdbId?: string | null;\n  titleSlug?: string | null;\n  rootFolderPath?: string | null;\n  folder?: string | null;\n  certification?: string | null;\n  genres?: string[] | null;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  /** @format date-time */\n  added?: string;\n  addOptions?: AddSeriesOptions;\n  ratings?: Ratings;\n  statistics?: SeriesStatisticsResource;\n  episodesChanged?: boolean | null;\n  /**\n   * @deprecated\n   * @format int32\n   */\n  languageProfileId?: number;\n}\n\nexport interface SeriesStatisticsResource {\n  /** @format int32 */\n  seasonCount?: number;\n  /** @format int32 */\n  episodeFileCount?: number;\n  /** @format int32 */\n  episodeCount?: number;\n  /** @format int32 */\n  totalEpisodeCount?: number;\n  /** @format int64 */\n  sizeOnDisk?: number;\n  releaseGroups?: string[] | null;\n  /** @format double */\n  percentOfEpisodes?: number;\n}\n\nexport interface SeriesTitleInfo {\n  title?: string | null;\n  titleWithoutYear?: string | null;\n  /** @format int32 */\n  year?: number;\n  allTitles?: string[] | null;\n}\n\nexport interface SystemResource {\n  appName?: string | null;\n  instanceName?: string | null;\n  version?: string | null;\n  /** @format date-time */\n  buildTime?: string;\n  isDebug?: boolean;\n  isProduction?: boolean;\n  isAdmin?: boolean;\n  isUserInteractive?: boolean;\n  startupPath?: string | null;\n  appData?: string | null;\n  osName?: string | null;\n  osVersion?: string | null;\n  isNetCore?: boolean;\n  isLinux?: boolean;\n  isOsx?: boolean;\n  isWindows?: boolean;\n  isDocker?: boolean;\n  mode?: RuntimeMode;\n  branch?: string | null;\n  authentication?: AuthenticationType;\n  sqliteVersion?: string | null;\n  /** @format int32 */\n  migrationVersion?: number;\n  urlBase?: string | null;\n  runtimeVersion?: string | null;\n  runtimeName?: string | null;\n  /** @format date-time */\n  startTime?: string;\n  packageVersion?: string | null;\n  packageAuthor?: string | null;\n  packageUpdateMechanism?: UpdateMechanism;\n  packageUpdateMechanismMessage?: string | null;\n  databaseVersion?: string | null;\n  databaseType?: DatabaseType;\n}\n\nexport interface TagDetailsResource {\n  /** @format int32 */\n  id?: number;\n  label?: string | null;\n  delayProfileIds?: number[] | null;\n  importListIds?: number[] | null;\n  notificationIds?: number[] | null;\n  restrictionIds?: number[] | null;\n  indexerIds?: number[] | null;\n  downloadClientIds?: number[] | null;\n  autoTagIds?: number[] | null;\n  seriesIds?: number[] | null;\n}\n\nexport interface TagResource {\n  /** @format int32 */\n  id?: number;\n  label?: string | null;\n}\n\nexport interface TaskResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  taskName?: string | null;\n  /** @format int32 */\n  interval?: number;\n  /** @format date-time */\n  lastExecution?: string;\n  /** @format date-time */\n  lastStartTime?: string;\n  /** @format date-time */\n  nextExecution?: string;\n  /** @format date-span */\n  lastDuration?: string;\n}\n\nexport interface TrackedDownloadStatusMessage {\n  title?: string | null;\n  messages?: string[] | null;\n}\n\nexport interface UiConfigResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  firstDayOfWeek?: number;\n  calendarWeekColumnHeader?: string | null;\n  shortDateFormat?: string | null;\n  longDateFormat?: string | null;\n  timeFormat?: string | null;\n  showRelativeDates?: boolean;\n  enableColorImpairedMode?: boolean;\n  theme?: string | null;\n  /** @format int32 */\n  uiLanguage?: number;\n}\n\nexport interface UnmappedFolder {\n  name?: string | null;\n  path?: string | null;\n  relativePath?: string | null;\n}\n\nexport interface UpdateChanges {\n  new?: string[] | null;\n  fixed?: string[] | null;\n}\n\nexport interface UpdateResource {\n  /** @format int32 */\n  id?: number;\n  version?: string | null;\n  branch?: string | null;\n  /** @format date-time */\n  releaseDate?: string;\n  fileName?: string | null;\n  url?: string | null;\n  installed?: boolean;\n  /** @format date-time */\n  installedOn?: string | null;\n  installable?: boolean;\n  latest?: boolean;\n  changes?: UpdateChanges;\n  hash?: string | null;\n}\n"
  },
  {
    "path": "src/__generated__/whisparr/Api.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { ContentType, HttpClient, RequestParams } from \"./../../ky-client\";\nimport {\n  AutoTaggingResource,\n  BackupResource,\n  BlocklistBulkResource,\n  BlocklistResourcePagingResource,\n  CommandResource,\n  CustomFilterResource,\n  CustomFormatResource,\n  DelayProfileResource,\n  DiskSpaceResource,\n  DownloadClientBulkResource,\n  DownloadClientConfigResource,\n  DownloadClientResource,\n  EpisodeFileListResource,\n  EpisodeFileResource,\n  EpisodeHistoryEventType,\n  EpisodeResource,\n  EpisodeResourcePagingResource,\n  EpisodesMonitoredResource,\n  HealthResource,\n  HistoryResource,\n  HistoryResourcePagingResource,\n  HostConfigResource,\n  ImportListBulkResource,\n  ImportListConfigResource,\n  ImportListExclusionResource,\n  ImportListResource,\n  IndexerBulkResource,\n  IndexerConfigResource,\n  IndexerResource,\n  LanguageProfileResource,\n  LanguageResource,\n  LocalizationLanguageResource,\n  LocalizationResource,\n  LogFileResource,\n  LogResourcePagingResource,\n  ManualImportReprocessResource,\n  ManualImportResource,\n  MediaManagementConfigResource,\n  MetadataResource,\n  NamingConfigResource,\n  NotificationResource,\n  ParseResource,\n  QualityDefinitionLimitsResource,\n  QualityDefinitionResource,\n  QualityProfileResource,\n  QueueBulkResource,\n  QueueResource,\n  QueueResourcePagingResource,\n  QueueStatusResource,\n  ReleaseProfileResource,\n  ReleaseResource,\n  RemotePathMappingResource,\n  RenameEpisodeResource,\n  RootFolderResource,\n  SeasonPassResource,\n  SeriesEditorResource,\n  SeriesResource,\n  SortDirection,\n  SystemResource,\n  TagDetailsResource,\n  TagResource,\n  TaskResource,\n  UiConfigResource,\n  UpdateResource,\n} from \"./data-contracts\";\n\nexport class Api<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags ApiInfo\n   * @name GetApi\n   * @request GET:/api\n   * @secure\n   */\n  getApi = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingCreate\n   * @request POST:/api/v3/autotagging\n   * @secure\n   */\n  v3AutotaggingCreate = (data: AutoTaggingResource, params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource, any>({\n      path: `/api/v3/autotagging`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingList\n   * @request GET:/api/v3/autotagging\n   * @secure\n   */\n  v3AutotaggingList = (params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource[], any>({\n      path: `/api/v3/autotagging`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingUpdate\n   * @request PUT:/api/v3/autotagging/{id}\n   * @secure\n   */\n  v3AutotaggingUpdate = (id: string, data: AutoTaggingResource, params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource, any>({\n      path: `/api/v3/autotagging/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingDelete\n   * @request DELETE:/api/v3/autotagging/{id}\n   * @secure\n   */\n  v3AutotaggingDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/autotagging/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingDetail\n   * @request GET:/api/v3/autotagging/{id}\n   * @secure\n   */\n  v3AutotaggingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<AutoTaggingResource, any>({\n      path: `/api/v3/autotagging/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags AutoTagging\n   * @name V3AutotaggingSchemaList\n   * @request GET:/api/v3/autotagging/schema\n   * @secure\n   */\n  v3AutotaggingSchemaList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/autotagging/schema`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V3SystemBackupList\n   * @request GET:/api/v3/system/backup\n   * @secure\n   */\n  v3SystemBackupList = (params: RequestParams = {}) =>\n    this.http.request<BackupResource[], any>({\n      path: `/api/v3/system/backup`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V3SystemBackupDelete\n   * @request DELETE:/api/v3/system/backup/{id}\n   * @secure\n   */\n  v3SystemBackupDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/backup/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V3SystemBackupRestoreCreate\n   * @request POST:/api/v3/system/backup/restore/{id}\n   * @secure\n   */\n  v3SystemBackupRestoreCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/backup/restore/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Backup\n   * @name V3SystemBackupRestoreUploadCreate\n   * @request POST:/api/v3/system/backup/restore/upload\n   * @secure\n   */\n  v3SystemBackupRestoreUploadCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/backup/restore/upload`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V3BlocklistList\n   * @request GET:/api/v3/blocklist\n   * @secure\n   */\n  v3BlocklistList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<BlocklistResourcePagingResource, any>({\n      path: `/api/v3/blocklist`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V3BlocklistDelete\n   * @request DELETE:/api/v3/blocklist/{id}\n   * @secure\n   */\n  v3BlocklistDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/blocklist/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Blocklist\n   * @name V3BlocklistBulkDelete\n   * @request DELETE:/api/v3/blocklist/bulk\n   * @secure\n   */\n  v3BlocklistBulkDelete = (data: BlocklistBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/blocklist/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Calendar\n   * @name V3CalendarList\n   * @request GET:/api/v3/calendar\n   * @secure\n   */\n  v3CalendarList = (\n    query?: {\n      /** @format date-time */\n      start?: string;\n      /** @format date-time */\n      end?: string;\n      /** @default false */\n      unmonitored?: boolean;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisodeFile?: boolean;\n      /** @default false */\n      includeEpisodeImages?: boolean;\n      /** @default \"\" */\n      tags?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<EpisodeResource[], any>({\n      path: `/api/v3/calendar`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Calendar\n   * @name V3CalendarDetail\n   * @request GET:/api/v3/calendar/{id}\n   * @secure\n   */\n  v3CalendarDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<EpisodeResource, any>({\n      path: `/api/v3/calendar/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V3CommandCreate\n   * @request POST:/api/v3/command\n   * @secure\n   */\n  v3CommandCreate = (data: CommandResource, params: RequestParams = {}) =>\n    this.http.request<CommandResource, any>({\n      path: `/api/v3/command`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V3CommandList\n   * @request GET:/api/v3/command\n   * @secure\n   */\n  v3CommandList = (params: RequestParams = {}) =>\n    this.http.request<CommandResource[], any>({\n      path: `/api/v3/command`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V3CommandDelete\n   * @request DELETE:/api/v3/command/{id}\n   * @secure\n   */\n  v3CommandDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/command/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Command\n   * @name V3CommandDetail\n   * @request GET:/api/v3/command/{id}\n   * @secure\n   */\n  v3CommandDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CommandResource, any>({\n      path: `/api/v3/command/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterList\n   * @request GET:/api/v3/customfilter\n   * @secure\n   */\n  v3CustomfilterList = (params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource[], any>({\n      path: `/api/v3/customfilter`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterCreate\n   * @request POST:/api/v3/customfilter\n   * @secure\n   */\n  v3CustomfilterCreate = (data: CustomFilterResource, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v3/customfilter`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterUpdate\n   * @request PUT:/api/v3/customfilter/{id}\n   * @secure\n   */\n  v3CustomfilterUpdate = (id: string, data: CustomFilterResource, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v3/customfilter/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterDelete\n   * @request DELETE:/api/v3/customfilter/{id}\n   * @secure\n   */\n  v3CustomfilterDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/customfilter/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFilter\n   * @name V3CustomfilterDetail\n   * @request GET:/api/v3/customfilter/{id}\n   * @secure\n   */\n  v3CustomfilterDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CustomFilterResource, any>({\n      path: `/api/v3/customfilter/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatCreate\n   * @request POST:/api/v3/customformat\n   * @secure\n   */\n  v3CustomformatCreate = (data: CustomFormatResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v3/customformat`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatList\n   * @request GET:/api/v3/customformat\n   * @secure\n   */\n  v3CustomformatList = (params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource[], any>({\n      path: `/api/v3/customformat`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatUpdate\n   * @request PUT:/api/v3/customformat/{id}\n   * @secure\n   */\n  v3CustomformatUpdate = (id: string, data: CustomFormatResource, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v3/customformat/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatDelete\n   * @request DELETE:/api/v3/customformat/{id}\n   * @secure\n   */\n  v3CustomformatDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/customformat/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatDetail\n   * @request GET:/api/v3/customformat/{id}\n   * @secure\n   */\n  v3CustomformatDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<CustomFormatResource, any>({\n      path: `/api/v3/customformat/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags CustomFormat\n   * @name V3CustomformatSchemaList\n   * @request GET:/api/v3/customformat/schema\n   * @secure\n   */\n  v3CustomformatSchemaList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/customformat/schema`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Cutoff\n   * @name V3WantedCutoffList\n   * @request GET:/api/v3/wanted/cutoff\n   * @secure\n   */\n  v3WantedCutoffList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisodeFile?: boolean;\n      /** @default false */\n      includeImages?: boolean;\n      /** @default true */\n      monitored?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<EpisodeResourcePagingResource, any>({\n      path: `/api/v3/wanted/cutoff`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Cutoff\n   * @name V3WantedCutoffDetail\n   * @request GET:/api/v3/wanted/cutoff/{id}\n   * @secure\n   */\n  v3WantedCutoffDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<EpisodeResource, any>({\n      path: `/api/v3/wanted/cutoff/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileCreate\n   * @request POST:/api/v3/delayprofile\n   * @secure\n   */\n  v3DelayprofileCreate = (data: DelayProfileResource, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v3/delayprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileList\n   * @request GET:/api/v3/delayprofile\n   * @secure\n   */\n  v3DelayprofileList = (params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource[], any>({\n      path: `/api/v3/delayprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileDelete\n   * @request DELETE:/api/v3/delayprofile/{id}\n   * @secure\n   */\n  v3DelayprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/delayprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileUpdate\n   * @request PUT:/api/v3/delayprofile/{id}\n   * @secure\n   */\n  v3DelayprofileUpdate = (id: string, data: DelayProfileResource, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v3/delayprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileDetail\n   * @request GET:/api/v3/delayprofile/{id}\n   * @secure\n   */\n  v3DelayprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DelayProfileResource, any>({\n      path: `/api/v3/delayprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DelayProfile\n   * @name V3DelayprofileReorderUpdate\n   * @request PUT:/api/v3/delayprofile/reorder/{id}\n   * @secure\n   */\n  v3DelayprofileReorderUpdate = (\n    id: number,\n    query?: {\n      /** @format int32 */\n      after?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DelayProfileResource[], any>({\n      path: `/api/v3/delayprofile/reorder/${id}`,\n      method: \"PUT\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DiskSpace\n   * @name V3DiskspaceList\n   * @request GET:/api/v3/diskspace\n   * @secure\n   */\n  v3DiskspaceList = (params: RequestParams = {}) =>\n    this.http.request<DiskSpaceResource[], any>({\n      path: `/api/v3/diskspace`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientList\n   * @request GET:/api/v3/downloadclient\n   * @secure\n   */\n  v3DownloadclientList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource[], any>({\n      path: `/api/v3/downloadclient`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientCreate\n   * @request POST:/api/v3/downloadclient\n   * @secure\n   */\n  v3DownloadclientCreate = (\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v3/downloadclient`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientUpdate\n   * @request PUT:/api/v3/downloadclient/{id}\n   * @secure\n   */\n  v3DownloadclientUpdate = (\n    id: string,\n    data: DownloadClientResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v3/downloadclient/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientDelete\n   * @request DELETE:/api/v3/downloadclient/{id}\n   * @secure\n   */\n  v3DownloadclientDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientDetail\n   * @request GET:/api/v3/downloadclient/{id}\n   * @secure\n   */\n  v3DownloadclientDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v3/downloadclient/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientBulkUpdate\n   * @request PUT:/api/v3/downloadclient/bulk\n   * @secure\n   */\n  v3DownloadclientBulkUpdate = (data: DownloadClientBulkResource, params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource, any>({\n      path: `/api/v3/downloadclient/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientBulkDelete\n   * @request DELETE:/api/v3/downloadclient/bulk\n   * @secure\n   */\n  v3DownloadclientBulkDelete = (data: DownloadClientBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientSchemaList\n   * @request GET:/api/v3/downloadclient/schema\n   * @secure\n   */\n  v3DownloadclientSchemaList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientResource[], any>({\n      path: `/api/v3/downloadclient/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientTestCreate\n   * @request POST:/api/v3/downloadclient/test\n   * @secure\n   */\n  v3DownloadclientTestCreate = (data: DownloadClientResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/test`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientTestallCreate\n   * @request POST:/api/v3/downloadclient/testall\n   * @secure\n   */\n  v3DownloadclientTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClient\n   * @name V3DownloadclientActionCreate\n   * @request POST:/api/v3/downloadclient/action/{name}\n   * @secure\n   */\n  v3DownloadclientActionCreate = (name: string, data: DownloadClientResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/downloadclient/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V3ConfigDownloadclientList\n   * @request GET:/api/v3/config/downloadclient\n   * @secure\n   */\n  v3ConfigDownloadclientList = (params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v3/config/downloadclient`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V3ConfigDownloadclientUpdate\n   * @request PUT:/api/v3/config/downloadclient/{id}\n   * @secure\n   */\n  v3ConfigDownloadclientUpdate = (id: string, data: DownloadClientConfigResource, params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v3/config/downloadclient/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags DownloadClientConfig\n   * @name V3ConfigDownloadclientDetail\n   * @request GET:/api/v3/config/downloadclient/{id}\n   * @secure\n   */\n  v3ConfigDownloadclientDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<DownloadClientConfigResource, any>({\n      path: `/api/v3/config/downloadclient/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Episode\n   * @name V3EpisodeList\n   * @request GET:/api/v3/episode\n   * @secure\n   */\n  v3EpisodeList = (\n    query?: {\n      /** @format int32 */\n      seriesId?: number;\n      /** @format int32 */\n      seasonNumber?: number;\n      episodeIds?: number[];\n      /** @format int32 */\n      episodeFileId?: number;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisodeFile?: boolean;\n      /** @default false */\n      includeImages?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<EpisodeResource[], any>({\n      path: `/api/v3/episode`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Episode\n   * @name V3EpisodeUpdate\n   * @request PUT:/api/v3/episode/{id}\n   * @secure\n   */\n  v3EpisodeUpdate = (id: number, data: EpisodeResource, params: RequestParams = {}) =>\n    this.http.request<EpisodeResource, any>({\n      path: `/api/v3/episode/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Episode\n   * @name V3EpisodeDetail\n   * @request GET:/api/v3/episode/{id}\n   * @secure\n   */\n  v3EpisodeDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<EpisodeResource, any>({\n      path: `/api/v3/episode/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Episode\n   * @name V3EpisodeMonitorUpdate\n   * @request PUT:/api/v3/episode/monitor\n   * @secure\n   */\n  v3EpisodeMonitorUpdate = (\n    data: EpisodesMonitoredResource,\n    query?: {\n      /** @default false */\n      includeImages?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/episode/monitor`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileList\n   * @request GET:/api/v3/episodefile\n   * @secure\n   */\n  v3EpisodefileList = (\n    query?: {\n      /** @format int32 */\n      seriesId?: number;\n      episodeFileIds?: number[];\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<EpisodeFileResource[], any>({\n      path: `/api/v3/episodefile`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileUpdate\n   * @request PUT:/api/v3/episodefile/{id}\n   * @secure\n   */\n  v3EpisodefileUpdate = (id: string, data: EpisodeFileResource, params: RequestParams = {}) =>\n    this.http.request<EpisodeFileResource, any>({\n      path: `/api/v3/episodefile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileDelete\n   * @request DELETE:/api/v3/episodefile/{id}\n   * @secure\n   */\n  v3EpisodefileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/episodefile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileDetail\n   * @request GET:/api/v3/episodefile/{id}\n   * @secure\n   */\n  v3EpisodefileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<EpisodeFileResource, any>({\n      path: `/api/v3/episodefile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileEditorUpdate\n   * @request PUT:/api/v3/episodefile/editor\n   * @secure\n   */\n  v3EpisodefileEditorUpdate = (data: EpisodeFileListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/episodefile/editor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileBulkDelete\n   * @request DELETE:/api/v3/episodefile/bulk\n   * @secure\n   */\n  v3EpisodefileBulkDelete = (data: EpisodeFileListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/episodefile/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags EpisodeFile\n   * @name V3EpisodefileBulkUpdate\n   * @request PUT:/api/v3/episodefile/bulk\n   * @secure\n   */\n  v3EpisodefileBulkUpdate = (data: EpisodeFileResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/episodefile/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V3FilesystemList\n   * @request GET:/api/v3/filesystem\n   * @secure\n   */\n  v3FilesystemList = (\n    query?: {\n      path?: string;\n      /** @default false */\n      includeFiles?: boolean;\n      /** @default false */\n      allowFoldersWithoutTrailingSlashes?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/filesystem`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V3FilesystemTypeList\n   * @request GET:/api/v3/filesystem/type\n   * @secure\n   */\n  v3FilesystemTypeList = (\n    query?: {\n      path?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/filesystem/type`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags FileSystem\n   * @name V3FilesystemMediafilesList\n   * @request GET:/api/v3/filesystem/mediafiles\n   * @secure\n   */\n  v3FilesystemMediafilesList = (\n    query?: {\n      path?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/filesystem/mediafiles`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Health\n   * @name V3HealthList\n   * @request GET:/api/v3/health\n   * @secure\n   */\n  v3HealthList = (params: RequestParams = {}) =>\n    this.http.request<HealthResource[], any>({\n      path: `/api/v3/health`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V3HistoryList\n   * @request GET:/api/v3/history\n   * @secure\n   */\n  v3HistoryList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      includeSeries?: boolean;\n      includeEpisode?: boolean;\n      /** @format int32 */\n      eventType?: number;\n      /** @format int32 */\n      episodeId?: number;\n      downloadId?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResourcePagingResource, any>({\n      path: `/api/v3/history`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V3HistorySinceList\n   * @request GET:/api/v3/history/since\n   * @secure\n   */\n  v3HistorySinceList = (\n    query?: {\n      /** @format date-time */\n      date?: string;\n      eventType?: EpisodeHistoryEventType;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisode?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResource[], any>({\n      path: `/api/v3/history/since`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V3HistorySeriesList\n   * @request GET:/api/v3/history/series\n   * @secure\n   */\n  v3HistorySeriesList = (\n    query?: {\n      /** @format int32 */\n      seriesId?: number;\n      /** @format int32 */\n      seasonNumber?: number;\n      eventType?: EpisodeHistoryEventType;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisode?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<HistoryResource[], any>({\n      path: `/api/v3/history/series`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags History\n   * @name V3HistoryFailedCreate\n   * @request POST:/api/v3/history/failed/{id}\n   * @secure\n   */\n  v3HistoryFailedCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/history/failed/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V3ConfigHostList\n   * @request GET:/api/v3/config/host\n   * @secure\n   */\n  v3ConfigHostList = (params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v3/config/host`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V3ConfigHostUpdate\n   * @request PUT:/api/v3/config/host/{id}\n   * @secure\n   */\n  v3ConfigHostUpdate = (id: string, data: HostConfigResource, params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v3/config/host/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags HostConfig\n   * @name V3ConfigHostDetail\n   * @request GET:/api/v3/config/host/{id}\n   * @secure\n   */\n  v3ConfigHostDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<HostConfigResource, any>({\n      path: `/api/v3/config/host/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistList\n   * @request GET:/api/v3/importlist\n   * @secure\n   */\n  v3ImportlistList = (params: RequestParams = {}) =>\n    this.http.request<ImportListResource[], any>({\n      path: `/api/v3/importlist`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistCreate\n   * @request POST:/api/v3/importlist\n   * @secure\n   */\n  v3ImportlistCreate = (\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v3/importlist`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistUpdate\n   * @request PUT:/api/v3/importlist/{id}\n   * @secure\n   */\n  v3ImportlistUpdate = (\n    id: string,\n    data: ImportListResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v3/importlist/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistDelete\n   * @request DELETE:/api/v3/importlist/{id}\n   * @secure\n   */\n  v3ImportlistDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistDetail\n   * @request GET:/api/v3/importlist/{id}\n   * @secure\n   */\n  v3ImportlistDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v3/importlist/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistBulkUpdate\n   * @request PUT:/api/v3/importlist/bulk\n   * @secure\n   */\n  v3ImportlistBulkUpdate = (data: ImportListBulkResource, params: RequestParams = {}) =>\n    this.http.request<ImportListResource, any>({\n      path: `/api/v3/importlist/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistBulkDelete\n   * @request DELETE:/api/v3/importlist/bulk\n   * @secure\n   */\n  v3ImportlistBulkDelete = (data: ImportListBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistSchemaList\n   * @request GET:/api/v3/importlist/schema\n   * @secure\n   */\n  v3ImportlistSchemaList = (params: RequestParams = {}) =>\n    this.http.request<ImportListResource[], any>({\n      path: `/api/v3/importlist/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistTestCreate\n   * @request POST:/api/v3/importlist/test\n   * @secure\n   */\n  v3ImportlistTestCreate = (data: ImportListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/test`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistTestallCreate\n   * @request POST:/api/v3/importlist/testall\n   * @secure\n   */\n  v3ImportlistTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportList\n   * @name V3ImportlistActionCreate\n   * @request POST:/api/v3/importlist/action/{name}\n   * @secure\n   */\n  v3ImportlistActionCreate = (name: string, data: ImportListResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlist/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListConfig\n   * @name V3ConfigImportlistList\n   * @request GET:/api/v3/config/importlist\n   * @secure\n   */\n  v3ConfigImportlistList = (params: RequestParams = {}) =>\n    this.http.request<ImportListConfigResource, any>({\n      path: `/api/v3/config/importlist`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListConfig\n   * @name V3ConfigImportlistUpdate\n   * @request PUT:/api/v3/config/importlist/{id}\n   * @secure\n   */\n  v3ConfigImportlistUpdate = (id: string, data: ImportListConfigResource, params: RequestParams = {}) =>\n    this.http.request<ImportListConfigResource, any>({\n      path: `/api/v3/config/importlist/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListConfig\n   * @name V3ConfigImportlistDetail\n   * @request GET:/api/v3/config/importlist/{id}\n   * @secure\n   */\n  v3ConfigImportlistDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListConfigResource, any>({\n      path: `/api/v3/config/importlist/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ImportlistexclusionList\n   * @request GET:/api/v3/importlistexclusion\n   * @secure\n   */\n  v3ImportlistexclusionList = (params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource[], any>({\n      path: `/api/v3/importlistexclusion`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ImportlistexclusionCreate\n   * @request POST:/api/v3/importlistexclusion\n   * @secure\n   */\n  v3ImportlistexclusionCreate = (data: ImportListExclusionResource, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v3/importlistexclusion`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ImportlistexclusionUpdate\n   * @request PUT:/api/v3/importlistexclusion/{id}\n   * @secure\n   */\n  v3ImportlistexclusionUpdate = (id: string, data: ImportListExclusionResource, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v3/importlistexclusion/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ImportlistexclusionDelete\n   * @request DELETE:/api/v3/importlistexclusion/{id}\n   * @secure\n   */\n  v3ImportlistexclusionDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/importlistexclusion/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ImportListExclusion\n   * @name V3ImportlistexclusionDetail\n   * @request GET:/api/v3/importlistexclusion/{id}\n   * @secure\n   */\n  v3ImportlistexclusionDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ImportListExclusionResource, any>({\n      path: `/api/v3/importlistexclusion/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerList\n   * @request GET:/api/v3/indexer\n   * @secure\n   */\n  v3IndexerList = (params: RequestParams = {}) =>\n    this.http.request<IndexerResource[], any>({\n      path: `/api/v3/indexer`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerCreate\n   * @request POST:/api/v3/indexer\n   * @secure\n   */\n  v3IndexerCreate = (\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v3/indexer`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerUpdate\n   * @request PUT:/api/v3/indexer/{id}\n   * @secure\n   */\n  v3IndexerUpdate = (\n    id: string,\n    data: IndexerResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v3/indexer/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerDelete\n   * @request DELETE:/api/v3/indexer/{id}\n   * @secure\n   */\n  v3IndexerDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerDetail\n   * @request GET:/api/v3/indexer/{id}\n   * @secure\n   */\n  v3IndexerDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v3/indexer/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerBulkUpdate\n   * @request PUT:/api/v3/indexer/bulk\n   * @secure\n   */\n  v3IndexerBulkUpdate = (data: IndexerBulkResource, params: RequestParams = {}) =>\n    this.http.request<IndexerResource, any>({\n      path: `/api/v3/indexer/bulk`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerBulkDelete\n   * @request DELETE:/api/v3/indexer/bulk\n   * @secure\n   */\n  v3IndexerBulkDelete = (data: IndexerBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/bulk`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerSchemaList\n   * @request GET:/api/v3/indexer/schema\n   * @secure\n   */\n  v3IndexerSchemaList = (params: RequestParams = {}) =>\n    this.http.request<IndexerResource[], any>({\n      path: `/api/v3/indexer/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerTestCreate\n   * @request POST:/api/v3/indexer/test\n   * @secure\n   */\n  v3IndexerTestCreate = (data: IndexerResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/test`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerTestallCreate\n   * @request POST:/api/v3/indexer/testall\n   * @secure\n   */\n  v3IndexerTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Indexer\n   * @name V3IndexerActionCreate\n   * @request POST:/api/v3/indexer/action/{name}\n   * @secure\n   */\n  v3IndexerActionCreate = (name: string, data: IndexerResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/indexer/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V3ConfigIndexerList\n   * @request GET:/api/v3/config/indexer\n   * @secure\n   */\n  v3ConfigIndexerList = (params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v3/config/indexer`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V3ConfigIndexerUpdate\n   * @request PUT:/api/v3/config/indexer/{id}\n   * @secure\n   */\n  v3ConfigIndexerUpdate = (id: string, data: IndexerConfigResource, params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v3/config/indexer/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags IndexerConfig\n   * @name V3ConfigIndexerDetail\n   * @request GET:/api/v3/config/indexer/{id}\n   * @secure\n   */\n  v3ConfigIndexerDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<IndexerConfigResource, any>({\n      path: `/api/v3/config/indexer/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Language\n   * @name V3LanguageList\n   * @request GET:/api/v3/language\n   * @secure\n   */\n  v3LanguageList = (params: RequestParams = {}) =>\n    this.http.request<LanguageResource[], any>({\n      path: `/api/v3/language`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Language\n   * @name V3LanguageDetail\n   * @request GET:/api/v3/language/{id}\n   * @secure\n   */\n  v3LanguageDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<LanguageResource, any>({\n      path: `/api/v3/language/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LanguageProfile\n   * @name V3LanguageprofileCreate\n   * @request POST:/api/v3/languageprofile\n   * @deprecated\n   * @secure\n   */\n  v3LanguageprofileCreate = (data: LanguageProfileResource, params: RequestParams = {}) =>\n    this.http.request<LanguageProfileResource, any>({\n      path: `/api/v3/languageprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LanguageProfile\n   * @name V3LanguageprofileList\n   * @request GET:/api/v3/languageprofile\n   * @deprecated\n   * @secure\n   */\n  v3LanguageprofileList = (params: RequestParams = {}) =>\n    this.http.request<LanguageProfileResource[], any>({\n      path: `/api/v3/languageprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LanguageProfile\n   * @name V3LanguageprofileDelete\n   * @request DELETE:/api/v3/languageprofile/{id}\n   * @deprecated\n   * @secure\n   */\n  v3LanguageprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/languageprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LanguageProfile\n   * @name V3LanguageprofileUpdate\n   * @request PUT:/api/v3/languageprofile/{id}\n   * @deprecated\n   * @secure\n   */\n  v3LanguageprofileUpdate = (id: string, data: LanguageProfileResource, params: RequestParams = {}) =>\n    this.http.request<LanguageProfileResource, any>({\n      path: `/api/v3/languageprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LanguageProfile\n   * @name V3LanguageprofileDetail\n   * @request GET:/api/v3/languageprofile/{id}\n   * @secure\n   */\n  v3LanguageprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<LanguageProfileResource, any>({\n      path: `/api/v3/languageprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LanguageProfileSchema\n   * @name V3LanguageprofileSchemaList\n   * @request GET:/api/v3/languageprofile/schema\n   * @deprecated\n   * @secure\n   */\n  v3LanguageprofileSchemaList = (params: RequestParams = {}) =>\n    this.http.request<LanguageProfileResource, any>({\n      path: `/api/v3/languageprofile/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Localization\n   * @name V3LocalizationList\n   * @request GET:/api/v3/localization\n   * @secure\n   */\n  v3LocalizationList = (params: RequestParams = {}) =>\n    this.http.request<LocalizationResource, any>({\n      path: `/api/v3/localization`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Localization\n   * @name V3LocalizationLanguageList\n   * @request GET:/api/v3/localization/language\n   * @secure\n   */\n  v3LocalizationLanguageList = (params: RequestParams = {}) =>\n    this.http.request<LocalizationLanguageResource, any>({\n      path: `/api/v3/localization/language`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Localization\n   * @name V3LocalizationDetail\n   * @request GET:/api/v3/localization/{id}\n   * @secure\n   */\n  v3LocalizationDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<LocalizationResource, any>({\n      path: `/api/v3/localization/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Log\n   * @name V3LogList\n   * @request GET:/api/v3/log\n   * @secure\n   */\n  v3LogList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      level?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<LogResourcePagingResource, any>({\n      path: `/api/v3/log`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LogFile\n   * @name V3LogFileList\n   * @request GET:/api/v3/log/file\n   * @secure\n   */\n  v3LogFileList = (params: RequestParams = {}) =>\n    this.http.request<LogFileResource[], any>({\n      path: `/api/v3/log/file`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags LogFile\n   * @name V3LogFileDetail\n   * @request GET:/api/v3/log/file/{filename}\n   * @secure\n   */\n  v3LogFileDetail = (filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/log/file/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ManualImport\n   * @name V3ManualimportList\n   * @request GET:/api/v3/manualimport\n   * @secure\n   */\n  v3ManualimportList = (\n    query?: {\n      folder?: string;\n      downloadId?: string;\n      /** @format int32 */\n      seriesId?: number;\n      /** @format int32 */\n      seasonNumber?: number;\n      /** @default true */\n      filterExistingFiles?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ManualImportResource[], any>({\n      path: `/api/v3/manualimport`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ManualImport\n   * @name V3ManualimportCreate\n   * @request POST:/api/v3/manualimport\n   * @secure\n   */\n  v3ManualimportCreate = (data: ManualImportReprocessResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/manualimport`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaCover\n   * @name V3MediacoverDetail\n   * @request GET:/api/v3/mediacover/{seriesId}/{filename}\n   * @secure\n   */\n  v3MediacoverDetail = (seriesId: number, filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/mediacover/${seriesId}/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V3ConfigMediamanagementList\n   * @request GET:/api/v3/config/mediamanagement\n   * @secure\n   */\n  v3ConfigMediamanagementList = (params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v3/config/mediamanagement`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V3ConfigMediamanagementUpdate\n   * @request PUT:/api/v3/config/mediamanagement/{id}\n   * @secure\n   */\n  v3ConfigMediamanagementUpdate = (id: string, data: MediaManagementConfigResource, params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v3/config/mediamanagement/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags MediaManagementConfig\n   * @name V3ConfigMediamanagementDetail\n   * @request GET:/api/v3/config/mediamanagement/{id}\n   * @secure\n   */\n  v3ConfigMediamanagementDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MediaManagementConfigResource, any>({\n      path: `/api/v3/config/mediamanagement/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataList\n   * @request GET:/api/v3/metadata\n   * @secure\n   */\n  v3MetadataList = (params: RequestParams = {}) =>\n    this.http.request<MetadataResource[], any>({\n      path: `/api/v3/metadata`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataCreate\n   * @request POST:/api/v3/metadata\n   * @secure\n   */\n  v3MetadataCreate = (\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v3/metadata`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataUpdate\n   * @request PUT:/api/v3/metadata/{id}\n   * @secure\n   */\n  v3MetadataUpdate = (\n    id: string,\n    data: MetadataResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v3/metadata/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataDelete\n   * @request DELETE:/api/v3/metadata/{id}\n   * @secure\n   */\n  v3MetadataDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/metadata/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataDetail\n   * @request GET:/api/v3/metadata/{id}\n   * @secure\n   */\n  v3MetadataDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<MetadataResource, any>({\n      path: `/api/v3/metadata/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataSchemaList\n   * @request GET:/api/v3/metadata/schema\n   * @secure\n   */\n  v3MetadataSchemaList = (params: RequestParams = {}) =>\n    this.http.request<MetadataResource[], any>({\n      path: `/api/v3/metadata/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataTestCreate\n   * @request POST:/api/v3/metadata/test\n   * @secure\n   */\n  v3MetadataTestCreate = (data: MetadataResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/metadata/test`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataTestallCreate\n   * @request POST:/api/v3/metadata/testall\n   * @secure\n   */\n  v3MetadataTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/metadata/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Metadata\n   * @name V3MetadataActionCreate\n   * @request POST:/api/v3/metadata/action/{name}\n   * @secure\n   */\n  v3MetadataActionCreate = (name: string, data: MetadataResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/metadata/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Missing\n   * @name V3WantedMissingList\n   * @request GET:/api/v3/wanted/missing\n   * @secure\n   */\n  v3WantedMissingList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeImages?: boolean;\n      /** @default true */\n      monitored?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<EpisodeResourcePagingResource, any>({\n      path: `/api/v3/wanted/missing`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Missing\n   * @name V3WantedMissingDetail\n   * @request GET:/api/v3/wanted/missing/{id}\n   * @secure\n   */\n  v3WantedMissingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<EpisodeResource, any>({\n      path: `/api/v3/wanted/missing/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V3ConfigNamingList\n   * @request GET:/api/v3/config/naming\n   * @secure\n   */\n  v3ConfigNamingList = (params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v3/config/naming`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V3ConfigNamingUpdate\n   * @request PUT:/api/v3/config/naming/{id}\n   * @secure\n   */\n  v3ConfigNamingUpdate = (id: string, data: NamingConfigResource, params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v3/config/naming/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V3ConfigNamingDetail\n   * @request GET:/api/v3/config/naming/{id}\n   * @secure\n   */\n  v3ConfigNamingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<NamingConfigResource, any>({\n      path: `/api/v3/config/naming/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags NamingConfig\n   * @name V3ConfigNamingExamplesList\n   * @request GET:/api/v3/config/naming/examples\n   * @secure\n   */\n  v3ConfigNamingExamplesList = (\n    query?: {\n      renameEpisodes?: boolean;\n      replaceIllegalCharacters?: boolean;\n      /** @format int32 */\n      colonReplacementFormat?: number;\n      /** @format int32 */\n      multiEpisodeStyle?: number;\n      standardEpisodeFormat?: string;\n      seriesFolderFormat?: string;\n      includeSeriesTitle?: boolean;\n      includeEpisodeTitle?: boolean;\n      includeQuality?: boolean;\n      replaceSpaces?: boolean;\n      separator?: string;\n      numberStyle?: string;\n      /** @format int32 */\n      id?: number;\n      resourceName?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/config/naming/examples`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationList\n   * @request GET:/api/v3/notification\n   * @secure\n   */\n  v3NotificationList = (params: RequestParams = {}) =>\n    this.http.request<NotificationResource[], any>({\n      path: `/api/v3/notification`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationCreate\n   * @request POST:/api/v3/notification\n   * @secure\n   */\n  v3NotificationCreate = (\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v3/notification`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationUpdate\n   * @request PUT:/api/v3/notification/{id}\n   * @secure\n   */\n  v3NotificationUpdate = (\n    id: string,\n    data: NotificationResource,\n    query?: {\n      /** @default false */\n      forceSave?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v3/notification/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationDelete\n   * @request DELETE:/api/v3/notification/{id}\n   * @secure\n   */\n  v3NotificationDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/notification/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationDetail\n   * @request GET:/api/v3/notification/{id}\n   * @secure\n   */\n  v3NotificationDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<NotificationResource, any>({\n      path: `/api/v3/notification/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationSchemaList\n   * @request GET:/api/v3/notification/schema\n   * @secure\n   */\n  v3NotificationSchemaList = (params: RequestParams = {}) =>\n    this.http.request<NotificationResource[], any>({\n      path: `/api/v3/notification/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationTestCreate\n   * @request POST:/api/v3/notification/test\n   * @secure\n   */\n  v3NotificationTestCreate = (data: NotificationResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/notification/test`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationTestallCreate\n   * @request POST:/api/v3/notification/testall\n   * @secure\n   */\n  v3NotificationTestallCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/notification/testall`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Notification\n   * @name V3NotificationActionCreate\n   * @request POST:/api/v3/notification/action/{name}\n   * @secure\n   */\n  v3NotificationActionCreate = (name: string, data: NotificationResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/notification/action/${name}`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Parse\n   * @name V3ParseList\n   * @request GET:/api/v3/parse\n   * @secure\n   */\n  v3ParseList = (\n    query?: {\n      title?: string;\n      path?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ParseResource, any>({\n      path: `/api/v3/parse`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionUpdate\n   * @request PUT:/api/v3/qualitydefinition/{id}\n   * @secure\n   */\n  v3QualitydefinitionUpdate = (id: string, data: QualityDefinitionResource, params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource, any>({\n      path: `/api/v3/qualitydefinition/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionDetail\n   * @request GET:/api/v3/qualitydefinition/{id}\n   * @secure\n   */\n  v3QualitydefinitionDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource, any>({\n      path: `/api/v3/qualitydefinition/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionList\n   * @request GET:/api/v3/qualitydefinition\n   * @secure\n   */\n  v3QualitydefinitionList = (params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionResource[], any>({\n      path: `/api/v3/qualitydefinition`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionUpdateUpdate\n   * @request PUT:/api/v3/qualitydefinition/update\n   * @secure\n   */\n  v3QualitydefinitionUpdateUpdate = (data: QualityDefinitionResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/qualitydefinition/update`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityDefinition\n   * @name V3QualitydefinitionLimitsList\n   * @request GET:/api/v3/qualitydefinition/limits\n   * @secure\n   */\n  v3QualitydefinitionLimitsList = (params: RequestParams = {}) =>\n    this.http.request<QualityDefinitionLimitsResource, any>({\n      path: `/api/v3/qualitydefinition/limits`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileCreate\n   * @request POST:/api/v3/qualityprofile\n   * @secure\n   */\n  v3QualityprofileCreate = (data: QualityProfileResource, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v3/qualityprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileList\n   * @request GET:/api/v3/qualityprofile\n   * @secure\n   */\n  v3QualityprofileList = (params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource[], any>({\n      path: `/api/v3/qualityprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileDelete\n   * @request DELETE:/api/v3/qualityprofile/{id}\n   * @secure\n   */\n  v3QualityprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/qualityprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileUpdate\n   * @request PUT:/api/v3/qualityprofile/{id}\n   * @secure\n   */\n  v3QualityprofileUpdate = (id: string, data: QualityProfileResource, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v3/qualityprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfile\n   * @name V3QualityprofileDetail\n   * @request GET:/api/v3/qualityprofile/{id}\n   * @secure\n   */\n  v3QualityprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v3/qualityprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QualityProfileSchema\n   * @name V3QualityprofileSchemaList\n   * @request GET:/api/v3/qualityprofile/schema\n   * @secure\n   */\n  v3QualityprofileSchemaList = (params: RequestParams = {}) =>\n    this.http.request<QualityProfileResource, any>({\n      path: `/api/v3/qualityprofile/schema`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V3QueueDelete\n   * @request DELETE:/api/v3/queue/{id}\n   * @secure\n   */\n  v3QueueDelete = (\n    id: number,\n    query?: {\n      /** @default true */\n      removeFromClient?: boolean;\n      /** @default false */\n      blocklist?: boolean;\n      /** @default false */\n      skipRedownload?: boolean;\n      /** @default false */\n      changeCategory?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/queue/${id}`,\n      method: \"DELETE\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V3QueueBulkDelete\n   * @request DELETE:/api/v3/queue/bulk\n   * @secure\n   */\n  v3QueueBulkDelete = (\n    data: QueueBulkResource,\n    query?: {\n      /** @default true */\n      removeFromClient?: boolean;\n      /** @default false */\n      blocklist?: boolean;\n      /** @default false */\n      skipRedownload?: boolean;\n      /** @default false */\n      changeCategory?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/queue/bulk`,\n      method: \"DELETE\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Queue\n   * @name V3QueueList\n   * @request GET:/api/v3/queue\n   * @secure\n   */\n  v3QueueList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 1\n       */\n      page?: number;\n      /**\n       * @format int32\n       * @default 10\n       */\n      pageSize?: number;\n      sortKey?: string;\n      sortDirection?: SortDirection;\n      /** @default false */\n      includeUnknownSeriesItems?: boolean;\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisode?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<QueueResourcePagingResource, any>({\n      path: `/api/v3/queue`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueAction\n   * @name V3QueueGrabCreate\n   * @request POST:/api/v3/queue/grab/{id}\n   * @secure\n   */\n  v3QueueGrabCreate = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/queue/grab/${id}`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueAction\n   * @name V3QueueGrabBulkCreate\n   * @request POST:/api/v3/queue/grab/bulk\n   * @secure\n   */\n  v3QueueGrabBulkCreate = (data: QueueBulkResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/queue/grab/bulk`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueDetails\n   * @name V3QueueDetailsList\n   * @request GET:/api/v3/queue/details\n   * @secure\n   */\n  v3QueueDetailsList = (\n    query?: {\n      /** @format int32 */\n      seriesId?: number;\n      episodeIds?: number[];\n      /** @default false */\n      includeSeries?: boolean;\n      /** @default false */\n      includeEpisode?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<QueueResource[], any>({\n      path: `/api/v3/queue/details`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags QueueStatus\n   * @name V3QueueStatusList\n   * @request GET:/api/v3/queue/status\n   * @secure\n   */\n  v3QueueStatusList = (params: RequestParams = {}) =>\n    this.http.request<QueueStatusResource, any>({\n      path: `/api/v3/queue/status`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Release\n   * @name V3ReleaseCreate\n   * @request POST:/api/v3/release\n   * @secure\n   */\n  v3ReleaseCreate = (data: ReleaseResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/release`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Release\n   * @name V3ReleaseList\n   * @request GET:/api/v3/release\n   * @secure\n   */\n  v3ReleaseList = (\n    query?: {\n      /** @format int32 */\n      seriesId?: number;\n      /** @format int32 */\n      episodeId?: number;\n      /** @format int32 */\n      seasonNumber?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<ReleaseResource[], any>({\n      path: `/api/v3/release`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileCreate\n   * @request POST:/api/v3/releaseprofile\n   * @secure\n   */\n  v3ReleaseprofileCreate = (data: ReleaseProfileResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v3/releaseprofile`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileList\n   * @request GET:/api/v3/releaseprofile\n   * @secure\n   */\n  v3ReleaseprofileList = (params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource[], any>({\n      path: `/api/v3/releaseprofile`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileDelete\n   * @request DELETE:/api/v3/releaseprofile/{id}\n   * @secure\n   */\n  v3ReleaseprofileDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/releaseprofile/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileUpdate\n   * @request PUT:/api/v3/releaseprofile/{id}\n   * @secure\n   */\n  v3ReleaseprofileUpdate = (id: string, data: ReleaseProfileResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v3/releaseprofile/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleaseProfile\n   * @name V3ReleaseprofileDetail\n   * @request GET:/api/v3/releaseprofile/{id}\n   * @secure\n   */\n  v3ReleaseprofileDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<ReleaseProfileResource, any>({\n      path: `/api/v3/releaseprofile/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags ReleasePush\n   * @name V3ReleasePushCreate\n   * @request POST:/api/v3/release/push\n   * @secure\n   */\n  v3ReleasePushCreate = (data: ReleaseResource, params: RequestParams = {}) =>\n    this.http.request<ReleaseResource[], any>({\n      path: `/api/v3/release/push`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingCreate\n   * @request POST:/api/v3/remotepathmapping\n   * @secure\n   */\n  v3RemotepathmappingCreate = (data: RemotePathMappingResource, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v3/remotepathmapping`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingList\n   * @request GET:/api/v3/remotepathmapping\n   * @secure\n   */\n  v3RemotepathmappingList = (params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource[], any>({\n      path: `/api/v3/remotepathmapping`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingDelete\n   * @request DELETE:/api/v3/remotepathmapping/{id}\n   * @secure\n   */\n  v3RemotepathmappingDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/remotepathmapping/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingUpdate\n   * @request PUT:/api/v3/remotepathmapping/{id}\n   * @secure\n   */\n  v3RemotepathmappingUpdate = (id: string, data: RemotePathMappingResource, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v3/remotepathmapping/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RemotePathMapping\n   * @name V3RemotepathmappingDetail\n   * @request GET:/api/v3/remotepathmapping/{id}\n   * @secure\n   */\n  v3RemotepathmappingDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<RemotePathMappingResource, any>({\n      path: `/api/v3/remotepathmapping/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RenameEpisode\n   * @name V3RenameList\n   * @request GET:/api/v3/rename\n   * @secure\n   */\n  v3RenameList = (\n    query?: {\n      /** @format int32 */\n      seriesId?: number;\n      /** @format int32 */\n      seasonNumber?: number;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<RenameEpisodeResource[], any>({\n      path: `/api/v3/rename`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V3RootfolderCreate\n   * @request POST:/api/v3/rootfolder\n   * @secure\n   */\n  v3RootfolderCreate = (data: RootFolderResource, params: RequestParams = {}) =>\n    this.http.request<RootFolderResource, any>({\n      path: `/api/v3/rootfolder`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V3RootfolderList\n   * @request GET:/api/v3/rootfolder\n   * @secure\n   */\n  v3RootfolderList = (params: RequestParams = {}) =>\n    this.http.request<RootFolderResource[], any>({\n      path: `/api/v3/rootfolder`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V3RootfolderDelete\n   * @request DELETE:/api/v3/rootfolder/{id}\n   * @secure\n   */\n  v3RootfolderDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/rootfolder/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags RootFolder\n   * @name V3RootfolderDetail\n   * @request GET:/api/v3/rootfolder/{id}\n   * @secure\n   */\n  v3RootfolderDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<RootFolderResource, any>({\n      path: `/api/v3/rootfolder/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags SeasonPass\n   * @name V3SeasonpassCreate\n   * @request POST:/api/v3/seasonpass\n   * @secure\n   */\n  v3SeasonpassCreate = (data: SeasonPassResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/seasonpass`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Series\n   * @name V3SeriesList\n   * @request GET:/api/v3/series\n   * @secure\n   */\n  v3SeriesList = (\n    query?: {\n      /** @format int32 */\n      tvdbId?: number;\n      /** @default false */\n      includeSeasonImages?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<SeriesResource[], any>({\n      path: `/api/v3/series`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Series\n   * @name V3SeriesCreate\n   * @request POST:/api/v3/series\n   * @secure\n   */\n  v3SeriesCreate = (data: SeriesResource, params: RequestParams = {}) =>\n    this.http.request<SeriesResource, any>({\n      path: `/api/v3/series`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Series\n   * @name V3SeriesDetail\n   * @request GET:/api/v3/series/{id}\n   * @secure\n   */\n  v3SeriesDetail = (\n    id: number,\n    query?: {\n      /** @default false */\n      includeSeasonImages?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<SeriesResource, any>({\n      path: `/api/v3/series/${id}`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Series\n   * @name V3SeriesUpdate\n   * @request PUT:/api/v3/series/{id}\n   * @secure\n   */\n  v3SeriesUpdate = (\n    id: string,\n    data: SeriesResource,\n    query?: {\n      /** @default false */\n      moveFiles?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<SeriesResource, any>({\n      path: `/api/v3/series/${id}`,\n      method: \"PUT\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Series\n   * @name V3SeriesDelete\n   * @request DELETE:/api/v3/series/{id}\n   * @secure\n   */\n  v3SeriesDelete = (\n    id: number,\n    query?: {\n      /** @default false */\n      deleteFiles?: boolean;\n      /** @default false */\n      addImportListExclusion?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/series/${id}`,\n      method: \"DELETE\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags SeriesEditor\n   * @name V3SeriesEditorUpdate\n   * @request PUT:/api/v3/series/editor\n   * @secure\n   */\n  v3SeriesEditorUpdate = (data: SeriesEditorResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/series/editor`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags SeriesEditor\n   * @name V3SeriesEditorDelete\n   * @request DELETE:/api/v3/series/editor\n   * @secure\n   */\n  v3SeriesEditorDelete = (data: SeriesEditorResource, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/series/editor`,\n      method: \"DELETE\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags SeriesImport\n   * @name V3SeriesImportCreate\n   * @request POST:/api/v3/series/import\n   * @secure\n   */\n  v3SeriesImportCreate = (data: SeriesResource[], params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/series/import`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags SeriesLookup\n   * @name V3SeriesLookupList\n   * @request GET:/api/v3/series/lookup\n   * @secure\n   */\n  v3SeriesLookupList = (\n    query?: {\n      term?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/api/v3/series/lookup`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemStatusList\n   * @request GET:/api/v3/system/status\n   * @secure\n   */\n  v3SystemStatusList = (params: RequestParams = {}) =>\n    this.http.request<SystemResource, any>({\n      path: `/api/v3/system/status`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemRoutesList\n   * @request GET:/api/v3/system/routes\n   * @secure\n   */\n  v3SystemRoutesList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/routes`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemRoutesDuplicateList\n   * @request GET:/api/v3/system/routes/duplicate\n   * @secure\n   */\n  v3SystemRoutesDuplicateList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/routes/duplicate`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemShutdownCreate\n   * @request POST:/api/v3/system/shutdown\n   * @secure\n   */\n  v3SystemShutdownCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/shutdown`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags System\n   * @name V3SystemRestartCreate\n   * @request POST:/api/v3/system/restart\n   * @secure\n   */\n  v3SystemRestartCreate = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/system/restart`,\n      method: \"POST\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagList\n   * @request GET:/api/v3/tag\n   * @secure\n   */\n  v3TagList = (params: RequestParams = {}) =>\n    this.http.request<TagResource[], any>({\n      path: `/api/v3/tag`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagCreate\n   * @request POST:/api/v3/tag\n   * @secure\n   */\n  v3TagCreate = (data: TagResource, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v3/tag`,\n      method: \"POST\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagUpdate\n   * @request PUT:/api/v3/tag/{id}\n   * @secure\n   */\n  v3TagUpdate = (id: string, data: TagResource, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v3/tag/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagDelete\n   * @request DELETE:/api/v3/tag/{id}\n   * @secure\n   */\n  v3TagDelete = (id: number, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/tag/${id}`,\n      method: \"DELETE\",\n      secure: true,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Tag\n   * @name V3TagDetail\n   * @request GET:/api/v3/tag/{id}\n   * @secure\n   */\n  v3TagDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TagResource, any>({\n      path: `/api/v3/tag/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TagDetails\n   * @name V3TagDetailList\n   * @request GET:/api/v3/tag/detail\n   * @secure\n   */\n  v3TagDetailList = (params: RequestParams = {}) =>\n    this.http.request<TagDetailsResource[], any>({\n      path: `/api/v3/tag/detail`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags TagDetails\n   * @name V3TagDetailDetail\n   * @request GET:/api/v3/tag/detail/{id}\n   * @secure\n   */\n  v3TagDetailDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TagDetailsResource, any>({\n      path: `/api/v3/tag/detail/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Task\n   * @name V3SystemTaskList\n   * @request GET:/api/v3/system/task\n   * @secure\n   */\n  v3SystemTaskList = (params: RequestParams = {}) =>\n    this.http.request<TaskResource[], any>({\n      path: `/api/v3/system/task`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Task\n   * @name V3SystemTaskDetail\n   * @request GET:/api/v3/system/task/{id}\n   * @secure\n   */\n  v3SystemTaskDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<TaskResource, any>({\n      path: `/api/v3/system/task/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V3ConfigUiUpdate\n   * @request PUT:/api/v3/config/ui/{id}\n   * @secure\n   */\n  v3ConfigUiUpdate = (id: string, data: UiConfigResource, params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v3/config/ui/${id}`,\n      method: \"PUT\",\n      body: data,\n      secure: true,\n      type: ContentType.Json,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V3ConfigUiDetail\n   * @request GET:/api/v3/config/ui/{id}\n   * @secure\n   */\n  v3ConfigUiDetail = (id: number, params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v3/config/ui/${id}`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UiConfig\n   * @name V3ConfigUiList\n   * @request GET:/api/v3/config/ui\n   * @secure\n   */\n  v3ConfigUiList = (params: RequestParams = {}) =>\n    this.http.request<UiConfigResource, any>({\n      path: `/api/v3/config/ui`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags Update\n   * @name V3UpdateList\n   * @request GET:/api/v3/update\n   * @secure\n   */\n  v3UpdateList = (params: RequestParams = {}) =>\n    this.http.request<UpdateResource[], any>({\n      path: `/api/v3/update`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UpdateLogFile\n   * @name V3LogFileUpdateList\n   * @request GET:/api/v3/log/file/update\n   * @secure\n   */\n  v3LogFileUpdateList = (params: RequestParams = {}) =>\n    this.http.request<LogFileResource[], any>({\n      path: `/api/v3/log/file/update`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags UpdateLogFile\n   * @name V3LogFileUpdateDetail\n   * @request GET:/api/v3/log/file/update/{filename}\n   * @secure\n   */\n  v3LogFileUpdateDetail = (filename: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/api/v3/log/file/update/${filename}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/whisparr/Content.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Content<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name ContentDetail\n   * @request GET:/content/{path}\n   * @secure\n   */\n  contentDetail = (path: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/content/${path}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/whisparr/Feed.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Feed<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags CalendarFeed\n   * @name V3CalendarWhisparrIcsList\n   * @request GET:/feed/v3/calendar/whisparr.ics\n   * @secure\n   */\n  v3CalendarWhisparrIcsList = (\n    query?: {\n      /**\n       * @format int32\n       * @default 7\n       */\n      pastDays?: number;\n      /**\n       * @format int32\n       * @default 28\n       */\n      futureDays?: number;\n      /** @default \"\" */\n      tags?: string;\n      /** @default false */\n      unmonitored?: boolean;\n      /** @default false */\n      premieresOnly?: boolean;\n      /** @default false */\n      asAllDay?: boolean;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/feed/v3/calendar/whisparr.ics`,\n      method: \"GET\",\n      query: query,\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/whisparr/Login.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { ContentType, HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Login<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Authentication\n   * @name LoginCreate\n   * @request POST:/login\n   * @secure\n   */\n  loginCreate = (\n    data: {\n      username?: string;\n      password?: string;\n      rememberMe?: string;\n    },\n    query?: {\n      returnUrl?: string;\n    },\n    params: RequestParams = {},\n  ) =>\n    this.http.request<void, any>({\n      path: `/login`,\n      method: \"POST\",\n      query: query,\n      body: data,\n      secure: true,\n      type: ContentType.FormData,\n      ...params,\n    });\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name LoginList\n   * @request GET:/login\n   * @secure\n   */\n  loginList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/login`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/whisparr/Logout.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Logout<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Authentication\n   * @name LogoutList\n   * @request GET:/logout\n   * @secure\n   */\n  logoutList = (params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/logout`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/whisparr/Path.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\n\nexport class Path<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags StaticResource\n   * @name GetPath\n   * @request GET:/{path}\n   * @secure\n   */\n  getPath = (path: string, params: RequestParams = {}) =>\n    this.http.request<void, any>({\n      path: `/${path}`,\n      method: \"GET\",\n      secure: true,\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/whisparr/Ping.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nimport { HttpClient, RequestParams } from \"./../../ky-client\";\nimport { PingResource } from \"./data-contracts\";\n\nexport class Ping<SecurityDataType = unknown> {\n  http: HttpClient<SecurityDataType>;\n\n  constructor(http: HttpClient<SecurityDataType>) {\n    this.http = http;\n  }\n\n  /**\n   * No description\n   *\n   * @tags Ping\n   * @name PingList\n   * @request GET:/ping\n   * @secure\n   */\n  pingList = (params: RequestParams = {}) =>\n    this.http.request<PingResource, any>({\n      path: `/ping`,\n      method: \"GET\",\n      secure: true,\n      format: \"json\",\n      ...params,\n    });\n}\n"
  },
  {
    "path": "src/__generated__/whisparr/data-contracts.ts",
    "content": "/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##\n * ##                                                           ##\n * ## AUTHOR: acacode                                           ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nexport enum UpdateMechanism {\n  BuiltIn = \"builtIn\",\n  Script = \"script\",\n  External = \"external\",\n  Apt = \"apt\",\n  Docker = \"docker\",\n}\n\nexport enum TrackedDownloadStatus {\n  Ok = \"ok\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum TrackedDownloadState {\n  Downloading = \"downloading\",\n  ImportPending = \"importPending\",\n  Importing = \"importing\",\n  Imported = \"imported\",\n  FailedPending = \"failedPending\",\n  Failed = \"failed\",\n  Ignored = \"ignored\",\n}\n\nexport enum SortDirection {\n  Default = \"default\",\n  Ascending = \"ascending\",\n  Descending = \"descending\",\n}\n\nexport enum SeriesStatusType {\n  Continuing = \"continuing\",\n  Ended = \"ended\",\n  Upcoming = \"upcoming\",\n  Deleted = \"deleted\",\n}\n\nexport enum RuntimeMode {\n  Console = \"console\",\n  Service = \"service\",\n  Tray = \"tray\",\n}\n\nexport enum RescanAfterRefreshType {\n  Always = \"always\",\n  AfterManual = \"afterManual\",\n  Never = \"never\",\n}\n\nexport enum RejectionType {\n  Permanent = \"permanent\",\n  Temporary = \"temporary\",\n}\n\nexport enum QualitySource {\n  Unknown = \"unknown\",\n  Television = \"television\",\n  TelevisionRaw = \"televisionRaw\",\n  Web = \"web\",\n  WebRip = \"webRip\",\n  Dvd = \"dvd\",\n  Bluray = \"bluray\",\n  Vr = \"vr\",\n  BlurayRaw = \"blurayRaw\",\n}\n\nexport enum ProxyType {\n  Http = \"http\",\n  Socks4 = \"socks4\",\n  Socks5 = \"socks5\",\n}\n\nexport enum ProviderMessageType {\n  Info = \"info\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum ProperDownloadTypes {\n  PreferAndUpgrade = \"preferAndUpgrade\",\n  DoNotUpgrade = \"doNotUpgrade\",\n  DoNotPrefer = \"doNotPrefer\",\n}\n\nexport enum PrivacyLevel {\n  Normal = \"normal\",\n  Password = \"password\",\n  ApiKey = \"apiKey\",\n  UserName = \"userName\",\n}\n\nexport enum MonitorTypes {\n  Unknown = \"unknown\",\n  All = \"all\",\n  Future = \"future\",\n  Missing = \"missing\",\n  Existing = \"existing\",\n  FirstSeason = \"firstSeason\",\n  LatestSeason = \"latestSeason\",\n  None = \"none\",\n}\n\nexport enum MediaCoverTypes {\n  Unknown = \"unknown\",\n  Logo = \"logo\",\n  Poster = \"poster\",\n  Banner = \"banner\",\n  Fanart = \"fanart\",\n  Screenshot = \"screenshot\",\n  Headshot = \"headshot\",\n  Clearlogo = \"clearlogo\",\n}\n\nexport enum ListSyncLevelType {\n  Disabled = \"disabled\",\n  LogOnly = \"logOnly\",\n  KeepAndUnmonitor = \"keepAndUnmonitor\",\n  KeepAndTag = \"keepAndTag\",\n}\n\nexport enum ImportListType {\n  Program = \"program\",\n  Plex = \"plex\",\n  Trakt = \"trakt\",\n  Simkl = \"simkl\",\n  Other = \"other\",\n  Advanced = \"advanced\",\n}\n\nexport enum ImportListMonitorTypes {\n  None = \"none\",\n  SpecificEpisode = \"specificEpisode\",\n  EntireSite = \"entireSite\",\n}\n\nexport enum HealthCheckResult {\n  Ok = \"ok\",\n  Notice = \"notice\",\n  Warning = \"warning\",\n  Error = \"error\",\n}\n\nexport enum Gender {\n  Female = \"female\",\n  Male = \"male\",\n  Other = \"other\",\n}\n\nexport enum FileDateType {\n  None = \"none\",\n  LocalAirDate = \"localAirDate\",\n  UtcAirDate = \"utcAirDate\",\n}\n\nexport enum EpisodeTitleRequiredType {\n  Always = \"always\",\n  BulkSeasonReleases = \"bulkSeasonReleases\",\n  Never = \"never\",\n}\n\nexport enum EpisodeHistoryEventType {\n  Unknown = \"unknown\",\n  Grabbed = \"grabbed\",\n  SeriesFolderImported = \"seriesFolderImported\",\n  DownloadFolderImported = \"downloadFolderImported\",\n  DownloadFailed = \"downloadFailed\",\n  EpisodeFileDeleted = \"episodeFileDeleted\",\n  EpisodeFileRenamed = \"episodeFileRenamed\",\n  DownloadIgnored = \"downloadIgnored\",\n}\n\nexport enum DownloadProtocol {\n  Unknown = \"unknown\",\n  Usenet = \"usenet\",\n  Torrent = \"torrent\",\n}\n\nexport enum DayOfWeek {\n  Sunday = \"sunday\",\n  Monday = \"monday\",\n  Tuesday = \"tuesday\",\n  Wednesday = \"wednesday\",\n  Thursday = \"thursday\",\n  Friday = \"friday\",\n  Saturday = \"saturday\",\n}\n\nexport enum DatabaseType {\n  SqLite = \"sqLite\",\n  PostgreSQL = \"postgreSQL\",\n}\n\nexport enum CommandTrigger {\n  Unspecified = \"unspecified\",\n  Manual = \"manual\",\n  Scheduled = \"scheduled\",\n}\n\nexport enum CommandStatus {\n  Queued = \"queued\",\n  Started = \"started\",\n  Completed = \"completed\",\n  Failed = \"failed\",\n  Aborted = \"aborted\",\n  Cancelled = \"cancelled\",\n  Orphaned = \"orphaned\",\n}\n\nexport enum CommandResult {\n  Unknown = \"unknown\",\n  Successful = \"successful\",\n  Unsuccessful = \"unsuccessful\",\n}\n\nexport enum CommandPriority {\n  Normal = \"normal\",\n  High = \"high\",\n  Low = \"low\",\n}\n\nexport enum CertificateValidationType {\n  Enabled = \"enabled\",\n  DisabledForLocalAddresses = \"disabledForLocalAddresses\",\n  Disabled = \"disabled\",\n}\n\nexport enum BackupType {\n  Scheduled = \"scheduled\",\n  Manual = \"manual\",\n  Update = \"update\",\n}\n\nexport enum AuthenticationType {\n  None = \"none\",\n  Basic = \"basic\",\n  Forms = \"forms\",\n  External = \"external\",\n}\n\nexport enum AuthenticationRequiredType {\n  Enabled = \"enabled\",\n  DisabledForLocalAddresses = \"disabledForLocalAddresses\",\n}\n\nexport enum ApplyTags {\n  Add = \"add\",\n  Remove = \"remove\",\n  Replace = \"replace\",\n}\n\nexport interface Actor {\n  /** @format int32 */\n  tpdbId?: number;\n  name?: string | null;\n  character?: string | null;\n  gender?: Gender;\n  images?: MediaCover[] | null;\n}\n\nexport interface AddSeriesOptions {\n  ignoreEpisodesWithFiles?: boolean;\n  ignoreEpisodesWithoutFiles?: boolean;\n  episodesToMonitor?: number[] | null;\n  monitor?: MonitorTypes;\n  searchForMissingEpisodes?: boolean;\n  searchForCutoffUnmetEpisodes?: boolean;\n}\n\nexport interface AutoTaggingResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  removeTagsAutomatically?: boolean;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  specifications?: AutoTaggingSpecificationSchema[] | null;\n}\n\nexport interface AutoTaggingSpecificationSchema {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  implementation?: string | null;\n  implementationName?: string | null;\n  negate?: boolean;\n  required?: boolean;\n  fields?: Field[] | null;\n}\n\nexport interface BackupResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  path?: string | null;\n  type?: BackupType;\n  /** @format int64 */\n  size?: number;\n  /** @format date-time */\n  time?: string;\n}\n\nexport interface BlocklistBulkResource {\n  ids?: number[] | null;\n}\n\nexport interface BlocklistResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  seriesId?: number;\n  episodeIds?: number[] | null;\n  sourceTitle?: string | null;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format date-time */\n  date?: string;\n  protocol?: DownloadProtocol;\n  indexer?: string | null;\n  message?: string | null;\n  series?: SeriesResource;\n}\n\nexport interface BlocklistResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: BlocklistResource[] | null;\n}\n\nexport interface Command {\n  sendUpdatesToClient?: boolean;\n  updateScheduledTask?: boolean;\n  completionMessage?: string | null;\n  requiresDiskAccess?: boolean;\n  isExclusive?: boolean;\n  isLongRunning?: boolean;\n  name?: string | null;\n  /** @format date-time */\n  lastExecutionTime?: string | null;\n  /** @format date-time */\n  lastStartTime?: string | null;\n  trigger?: CommandTrigger;\n  suppressMessages?: boolean;\n  clientUserAgent?: string | null;\n}\n\nexport interface CommandResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  commandName?: string | null;\n  message?: string | null;\n  body?: Command;\n  priority?: CommandPriority;\n  status?: CommandStatus;\n  result?: CommandResult;\n  /** @format date-time */\n  queued?: string;\n  /** @format date-time */\n  started?: string | null;\n  /** @format date-time */\n  ended?: string | null;\n  duration?: TimeSpan;\n  exception?: string | null;\n  trigger?: CommandTrigger;\n  clientUserAgent?: string | null;\n  /** @format date-time */\n  stateChangeTime?: string | null;\n  sendUpdatesToClient?: boolean;\n  updateScheduledTask?: boolean;\n  /** @format date-time */\n  lastExecutionTime?: string | null;\n}\n\nexport interface CustomFilterResource {\n  /** @format int32 */\n  id?: number;\n  type?: string | null;\n  label?: string | null;\n  filters?: Record<string, any>[] | null;\n}\n\nexport interface CustomFormatResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  includeCustomFormatWhenRenaming?: boolean | null;\n  specifications?: CustomFormatSpecificationSchema[] | null;\n}\n\nexport interface CustomFormatSpecificationSchema {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  implementation?: string | null;\n  implementationName?: string | null;\n  infoLink?: string | null;\n  negate?: boolean;\n  required?: boolean;\n  fields?: Field[] | null;\n  presets?: CustomFormatSpecificationSchema[] | null;\n}\n\nexport interface DateOnly {\n  /** @format int32 */\n  year?: number;\n  /** @format int32 */\n  month?: number;\n  /** @format int32 */\n  day?: number;\n  dayOfWeek?: DayOfWeek;\n  /** @format int32 */\n  dayOfYear?: number;\n  /** @format int32 */\n  dayNumber?: number;\n}\n\nexport interface DelayProfileResource {\n  /** @format int32 */\n  id?: number;\n  enableUsenet?: boolean;\n  enableTorrent?: boolean;\n  preferredProtocol?: DownloadProtocol;\n  /** @format int32 */\n  usenetDelay?: number;\n  /** @format int32 */\n  torrentDelay?: number;\n  bypassIfHighestQuality?: boolean;\n  bypassIfAboveCustomFormatScore?: boolean;\n  /** @format int32 */\n  minimumCustomFormatScore?: number;\n  /** @format int32 */\n  order?: number;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n}\n\nexport interface DiskSpaceResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  label?: string | null;\n  /** @format int64 */\n  freeSpace?: number;\n  /** @format int64 */\n  totalSpace?: number;\n}\n\nexport interface DownloadClientBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enable?: boolean | null;\n  /** @format int32 */\n  priority?: number | null;\n  removeCompletedDownloads?: boolean | null;\n  removeFailedDownloads?: boolean | null;\n}\n\nexport interface DownloadClientConfigResource {\n  /** @format int32 */\n  id?: number;\n  downloadClientWorkingFolders?: string | null;\n  enableCompletedDownloadHandling?: boolean;\n  autoRedownloadFailed?: boolean;\n}\n\nexport interface DownloadClientResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: DownloadClientResource[] | null;\n  enable?: boolean;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  priority?: number;\n  removeCompletedDownloads?: boolean;\n  removeFailedDownloads?: boolean;\n}\n\nexport interface EpisodeFileListResource {\n  episodeFileIds?: number[] | null;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  sceneName?: string | null;\n  releaseGroup?: string | null;\n}\n\nexport interface EpisodeFileResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  seriesId?: number;\n  /** @format int32 */\n  seasonNumber?: number;\n  relativePath?: string | null;\n  path?: string | null;\n  /** @format int64 */\n  size?: number;\n  /** @format date-time */\n  dateAdded?: string;\n  sceneName?: string | null;\n  releaseGroup?: string | null;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  mediaInfo?: MediaInfoResource;\n  qualityCutoffNotMet?: boolean;\n}\n\nexport interface EpisodeResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  seriesId?: number;\n  /** @format int32 */\n  tvdbId?: number;\n  /** @format int32 */\n  episodeFileId?: number;\n  /** @format int32 */\n  seasonNumber?: number;\n  title?: string | null;\n  releaseDate?: DateOnly;\n  /** @format date-time */\n  lastSearchTime?: string | null;\n  /** @format int32 */\n  runtime?: number;\n  overview?: string | null;\n  episodeFile?: EpisodeFileResource;\n  hasFile?: boolean;\n  monitored?: boolean;\n  /** @format int32 */\n  absoluteEpisodeNumber?: number | null;\n  /** @format date-time */\n  endTime?: string | null;\n  /** @format date-time */\n  grabDate?: string | null;\n  seriesTitle?: string | null;\n  series?: SeriesResource;\n  actors?: Actor[] | null;\n  images?: MediaCover[] | null;\n  grabbed?: boolean;\n}\n\nexport interface EpisodeResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: EpisodeResource[] | null;\n}\n\nexport interface EpisodesMonitoredResource {\n  episodeIds?: number[] | null;\n  monitored?: boolean;\n}\n\nexport interface Field {\n  /** @format int32 */\n  order?: number;\n  name?: string | null;\n  label?: string | null;\n  unit?: string | null;\n  helpText?: string | null;\n  helpTextWarning?: string | null;\n  helpLink?: string | null;\n  value?: any;\n  type?: string | null;\n  advanced?: boolean;\n  selectOptions?: SelectOption[] | null;\n  selectOptionsProviderAction?: string | null;\n  section?: string | null;\n  hidden?: string | null;\n  privacy?: PrivacyLevel;\n  placeholder?: string | null;\n  isFloat?: boolean;\n}\n\nexport interface HealthResource {\n  /** @format int32 */\n  id?: number;\n  source?: string | null;\n  type?: HealthCheckResult;\n  message?: string | null;\n  wikiUrl?: HttpUri;\n}\n\nexport interface HistoryResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  episodeId?: number;\n  /** @format int32 */\n  seriesId?: number;\n  sourceTitle?: string | null;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  qualityCutoffNotMet?: boolean;\n  /** @format date-time */\n  date?: string;\n  downloadId?: string | null;\n  eventType?: EpisodeHistoryEventType;\n  data?: Record<string, string | null>;\n  episode?: EpisodeResource;\n  series?: SeriesResource;\n}\n\nexport interface HistoryResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: HistoryResource[] | null;\n}\n\nexport interface HostConfigResource {\n  /** @format int32 */\n  id?: number;\n  bindAddress?: string | null;\n  /** @format int32 */\n  port?: number;\n  /** @format int32 */\n  sslPort?: number;\n  enableSsl?: boolean;\n  launchBrowser?: boolean;\n  authenticationMethod?: AuthenticationType;\n  authenticationRequired?: AuthenticationRequiredType;\n  analyticsEnabled?: boolean;\n  username?: string | null;\n  password?: string | null;\n  logLevel?: string | null;\n  logSizeLimit?: number | null;\n  consoleLogLevel?: string | null;\n  branch?: string | null;\n  apiKey?: string | null;\n  sslCertPath?: string | null;\n  sslCertPassword?: string | null;\n  urlBase?: string | null;\n  instanceName?: string | null;\n  applicationUrl?: string | null;\n  updateAutomatically?: boolean;\n  updateMechanism?: UpdateMechanism;\n  updateScriptPath?: string | null;\n  proxyEnabled?: boolean;\n  proxyType?: ProxyType;\n  proxyHostname?: string | null;\n  /** @format int32 */\n  proxyPort?: number;\n  proxyUsername?: string | null;\n  proxyPassword?: string | null;\n  proxyBypassFilter?: string | null;\n  proxyBypassLocalAddresses?: boolean;\n  certificateValidation?: CertificateValidationType;\n  backupFolder?: string | null;\n  /** @format int32 */\n  backupInterval?: number;\n  /** @format int32 */\n  backupRetention?: number;\n}\n\nexport interface HttpUri {\n  fullUri?: string | null;\n  scheme?: string | null;\n  host?: string | null;\n  /** @format int32 */\n  port?: number | null;\n  path?: string | null;\n  query?: string | null;\n  fragment?: string | null;\n}\n\nexport interface ImportListBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enableAutomaticAdd?: boolean | null;\n  rootFolderPath?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number | null;\n}\n\nexport interface ImportListConfigResource {\n  /** @format int32 */\n  id?: number;\n  listSyncLevel?: ListSyncLevelType;\n  /** @format int32 */\n  listSyncTag?: number;\n}\n\nexport interface ImportListExclusionResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  tvdbId?: number;\n  title?: string | null;\n}\n\nexport interface ImportListResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: ImportListResource[] | null;\n  enableAutomaticAdd?: boolean;\n  shouldMonitor?: ImportListMonitorTypes;\n  siteMonitorType?: MonitorTypes;\n  rootFolderPath?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number;\n  listType?: ImportListType;\n  /** @format int32 */\n  listOrder?: number;\n  minRefreshInterval?: TimeSpan;\n}\n\nexport interface IndexerBulkResource {\n  ids?: number[] | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  enableRss?: boolean | null;\n  enableAutomaticSearch?: boolean | null;\n  enableInteractiveSearch?: boolean | null;\n  /** @format int32 */\n  priority?: number | null;\n}\n\nexport interface IndexerConfigResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  minimumAge?: number;\n  /** @format int32 */\n  retention?: number;\n  /** @format int32 */\n  maximumSize?: number;\n  /** @format int32 */\n  rssSyncInterval?: number;\n}\n\nexport interface IndexerResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: IndexerResource[] | null;\n  enableRss?: boolean;\n  enableAutomaticSearch?: boolean;\n  enableInteractiveSearch?: boolean;\n  supportsRss?: boolean;\n  supportsSearch?: boolean;\n  protocol?: DownloadProtocol;\n  /** @format int32 */\n  priority?: number;\n  /** @format int32 */\n  seasonSearchMaximumSingleEpisodeAge?: number;\n  /** @format int32 */\n  downloadClientId?: number;\n}\n\nexport interface Language {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n}\n\nexport interface LanguageProfileItemResource {\n  /** @format int32 */\n  id?: number;\n  language?: Language;\n  allowed?: boolean;\n}\n\nexport interface LanguageProfileResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  upgradeAllowed?: boolean;\n  cutoff?: Language;\n  languages?: LanguageProfileItemResource[] | null;\n}\n\nexport interface LanguageResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  nameLower?: string | null;\n}\n\nexport interface LocalizationLanguageResource {\n  identifier?: string | null;\n}\n\nexport interface LocalizationResource {\n  /** @format int32 */\n  id?: number;\n  strings?: Record<string, string | null>;\n}\n\nexport interface LogFileResource {\n  /** @format int32 */\n  id?: number;\n  filename?: string | null;\n  /** @format date-time */\n  lastWriteTime?: string;\n  contentsUrl?: string | null;\n  downloadUrl?: string | null;\n}\n\nexport interface LogResource {\n  /** @format int32 */\n  id?: number;\n  /** @format date-time */\n  time?: string;\n  exception?: string | null;\n  exceptionType?: string | null;\n  level?: string | null;\n  logger?: string | null;\n  message?: string | null;\n  method?: string | null;\n}\n\nexport interface LogResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: LogResource[] | null;\n}\n\nexport interface ManualImportReprocessResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  /** @format int32 */\n  seriesId?: number;\n  /** @format int32 */\n  seasonNumber?: number | null;\n  episodes?: EpisodeResource[] | null;\n  episodeIds?: number[] | null;\n  quality?: QualityModel;\n  languages?: Language[] | null;\n  releaseGroup?: string | null;\n  downloadId?: string | null;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  rejections?: Rejection[] | null;\n}\n\nexport interface ManualImportResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  relativePath?: string | null;\n  folderName?: string | null;\n  name?: string | null;\n  /** @format int64 */\n  size?: number;\n  series?: SeriesResource;\n  /** @format int32 */\n  seasonNumber?: number | null;\n  episodes?: EpisodeResource[] | null;\n  /** @format int32 */\n  episodeFileId?: number | null;\n  releaseGroup?: string | null;\n  quality?: QualityModel;\n  languages?: Language[] | null;\n  /** @format int32 */\n  qualityWeight?: number;\n  downloadId?: string | null;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  rejections?: Rejection[] | null;\n}\n\nexport interface MediaCover {\n  coverType?: MediaCoverTypes;\n  url?: string | null;\n  remoteUrl?: string | null;\n}\n\nexport interface MediaInfoResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int64 */\n  audioBitrate?: number;\n  /** @format double */\n  audioChannels?: number;\n  audioCodec?: string | null;\n  audioLanguages?: string | null;\n  /** @format int32 */\n  audioStreamCount?: number;\n  /** @format int32 */\n  videoBitDepth?: number;\n  /** @format int64 */\n  videoBitrate?: number;\n  videoCodec?: string | null;\n  /** @format double */\n  videoFps?: number;\n  videoDynamicRange?: string | null;\n  videoDynamicRangeType?: string | null;\n  resolution?: string | null;\n  runTime?: string | null;\n  scanType?: string | null;\n  subtitles?: string | null;\n}\n\nexport interface MediaManagementConfigResource {\n  /** @format int32 */\n  id?: number;\n  autoUnmonitorPreviouslyDownloadedEpisodes?: boolean;\n  recycleBin?: string | null;\n  /** @format int32 */\n  recycleBinCleanupDays?: number;\n  downloadPropersAndRepacks?: ProperDownloadTypes;\n  createEmptySeriesFolders?: boolean;\n  deleteEmptyFolders?: boolean;\n  fileDate?: FileDateType;\n  rescanAfterRefresh?: RescanAfterRefreshType;\n  setPermissionsLinux?: boolean;\n  chmodFolder?: string | null;\n  chownGroup?: string | null;\n  episodeTitleRequired?: EpisodeTitleRequiredType;\n  skipFreeSpaceCheckWhenImporting?: boolean;\n  /** @format int32 */\n  minimumFreeSpaceWhenImporting?: number;\n  copyUsingHardlinks?: boolean;\n  useScriptImport?: boolean;\n  scriptImportPath?: string | null;\n  importExtraFiles?: boolean;\n  extraFileExtensions?: string | null;\n  enableMediaInfo?: boolean;\n}\n\nexport interface MetadataResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: MetadataResource[] | null;\n  enable?: boolean;\n}\n\nexport interface MonitoringOptions {\n  ignoreEpisodesWithFiles?: boolean;\n  ignoreEpisodesWithoutFiles?: boolean;\n  episodesToMonitor?: number[] | null;\n  monitor?: MonitorTypes;\n}\n\nexport interface NamingConfigResource {\n  /** @format int32 */\n  id?: number;\n  renameEpisodes?: boolean;\n  replaceIllegalCharacters?: boolean;\n  /** @format int32 */\n  colonReplacementFormat?: number;\n  /** @format int32 */\n  multiEpisodeStyle?: number;\n  standardEpisodeFormat?: string | null;\n  seriesFolderFormat?: string | null;\n  includeSeriesTitle?: boolean;\n  includeEpisodeTitle?: boolean;\n  includeQuality?: boolean;\n  replaceSpaces?: boolean;\n  separator?: string | null;\n  numberStyle?: string | null;\n}\n\nexport interface NotificationResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  fields?: Field[] | null;\n  implementationName?: string | null;\n  implementation?: string | null;\n  configContract?: string | null;\n  infoLink?: string | null;\n  message?: ProviderMessage;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  presets?: NotificationResource[] | null;\n  link?: string | null;\n  onGrab?: boolean;\n  onDownload?: boolean;\n  onUpgrade?: boolean;\n  onRename?: boolean;\n  onSeriesAdd?: boolean;\n  onSeriesDelete?: boolean;\n  onEpisodeFileDelete?: boolean;\n  onEpisodeFileDeleteForUpgrade?: boolean;\n  onHealthIssue?: boolean;\n  onHealthRestored?: boolean;\n  onApplicationUpdate?: boolean;\n  onManualInteractionRequired?: boolean;\n  supportsOnGrab?: boolean;\n  supportsOnDownload?: boolean;\n  supportsOnUpgrade?: boolean;\n  supportsOnRename?: boolean;\n  supportsOnSeriesAdd?: boolean;\n  supportsOnSeriesDelete?: boolean;\n  supportsOnEpisodeFileDelete?: boolean;\n  supportsOnEpisodeFileDeleteForUpgrade?: boolean;\n  supportsOnHealthIssue?: boolean;\n  supportsOnHealthRestored?: boolean;\n  supportsOnApplicationUpdate?: boolean;\n  supportsOnManualInteractionRequired?: boolean;\n  includeHealthWarnings?: boolean;\n  testCommand?: string | null;\n}\n\nexport interface ParseResource {\n  /** @format int32 */\n  id?: number;\n  title?: string | null;\n  parsedEpisodeInfo?: ParsedEpisodeInfo;\n  series?: SeriesResource;\n  episodes?: EpisodeResource[] | null;\n  languages?: Language[] | null;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n}\n\nexport interface ParsedEpisodeInfo {\n  releaseTitle?: string | null;\n  seriesTitle?: string | null;\n  seriesTitleInfo?: SeriesTitleInfo;\n  quality?: QualityModel;\n  airDate?: string | null;\n  languages?: Language[] | null;\n  releaseGroup?: string | null;\n  releaseHash?: string | null;\n  /** @format int32 */\n  seasonPart?: number;\n  releaseTokens?: string | null;\n  isDaily?: boolean;\n}\n\nexport interface PingResource {\n  status?: string | null;\n}\n\nexport interface ProfileFormatItemResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  format?: number;\n  name?: string | null;\n  /** @format int32 */\n  score?: number;\n}\n\nexport interface ProviderMessage {\n  message?: string | null;\n  type?: ProviderMessageType;\n}\n\nexport interface Quality {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  source?: QualitySource;\n  /** @format int32 */\n  resolution?: number;\n}\n\nexport interface QualityDefinitionLimitsResource {\n  /** @format int32 */\n  min?: number;\n  /** @format int32 */\n  max?: number;\n}\n\nexport interface QualityDefinitionResource {\n  /** @format int32 */\n  id?: number;\n  quality?: Quality;\n  title?: string | null;\n  /** @format int32 */\n  weight?: number;\n  /** @format double */\n  minSize?: number | null;\n  /** @format double */\n  maxSize?: number | null;\n  /** @format double */\n  preferredSize?: number | null;\n}\n\nexport interface QualityModel {\n  quality?: Quality;\n  revision?: Revision;\n}\n\nexport interface QualityProfileQualityItemResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  quality?: Quality;\n  items?: QualityProfileQualityItemResource[] | null;\n  allowed?: boolean;\n}\n\nexport interface QualityProfileResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  upgradeAllowed?: boolean;\n  /** @format int32 */\n  cutoff?: number;\n  items?: QualityProfileQualityItemResource[] | null;\n  /** @format int32 */\n  minFormatScore?: number;\n  /** @format int32 */\n  cutoffFormatScore?: number;\n  /** @format int32 */\n  minUpgradeFormatScore?: number;\n  formatItems?: ProfileFormatItemResource[] | null;\n}\n\nexport interface QueueBulkResource {\n  ids?: number[] | null;\n}\n\nexport interface QueueResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  seriesId?: number | null;\n  /** @format int32 */\n  episodeId?: number | null;\n  /** @format int32 */\n  seasonNumber?: number | null;\n  series?: SeriesResource;\n  episode?: EpisodeResource;\n  languages?: Language[] | null;\n  quality?: QualityModel;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  /** @format double */\n  size?: number;\n  title?: string | null;\n  /** @format double */\n  sizeleft?: number;\n  timeleft?: TimeSpan;\n  /** @format date-time */\n  estimatedCompletionTime?: string | null;\n  status?: string | null;\n  trackedDownloadStatus?: TrackedDownloadStatus;\n  trackedDownloadState?: TrackedDownloadState;\n  statusMessages?: TrackedDownloadStatusMessage[] | null;\n  errorMessage?: string | null;\n  downloadId?: string | null;\n  protocol?: DownloadProtocol;\n  downloadClient?: string | null;\n  downloadClientHasPostImportCategory?: boolean;\n  indexer?: string | null;\n  outputPath?: string | null;\n  episodeHasFile?: boolean;\n}\n\nexport interface QueueResourcePagingResource {\n  /** @format int32 */\n  page?: number;\n  /** @format int32 */\n  pageSize?: number;\n  sortKey?: string | null;\n  sortDirection?: SortDirection;\n  /** @format int32 */\n  totalRecords?: number;\n  records?: QueueResource[] | null;\n}\n\nexport interface QueueStatusResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  totalCount?: number;\n  /** @format int32 */\n  count?: number;\n  /** @format int32 */\n  unknownCount?: number;\n  errors?: boolean;\n  warnings?: boolean;\n  unknownErrors?: boolean;\n  unknownWarnings?: boolean;\n}\n\nexport interface Ratings {\n  /** @format int32 */\n  votes?: number;\n  /** @format double */\n  value?: number;\n}\n\nexport interface Rejection {\n  reason?: string | null;\n  type?: RejectionType;\n}\n\nexport interface ReleaseEpisodeResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  seasonNumber?: number;\n  /** @format int32 */\n  episodeNumber?: number;\n  /** @format int32 */\n  absoluteEpisodeNumber?: number | null;\n  title?: string | null;\n}\n\nexport interface ReleaseProfileResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  enabled?: boolean;\n  required?: any;\n  ignored?: any;\n  /** @format int32 */\n  indexerId?: number;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n}\n\nexport interface ReleaseResource {\n  /** @format int32 */\n  id?: number;\n  guid?: string | null;\n  quality?: QualityModel;\n  /** @format int32 */\n  qualityWeight?: number;\n  /** @format int32 */\n  age?: number;\n  /** @format double */\n  ageHours?: number;\n  /** @format double */\n  ageMinutes?: number;\n  /** @format int64 */\n  size?: number;\n  /** @format int32 */\n  indexerId?: number;\n  indexer?: string | null;\n  releaseGroup?: string | null;\n  subGroup?: string | null;\n  releaseHash?: string | null;\n  title?: string | null;\n  sceneSource?: boolean;\n  languages?: Language[] | null;\n  /** @format int32 */\n  languageWeight?: number;\n  airDate?: string | null;\n  seriesTitle?: string | null;\n  /** @format int32 */\n  mappedSeriesId?: number | null;\n  mappedEpisodeInfo?: ReleaseEpisodeResource[] | null;\n  approved?: boolean;\n  temporarilyRejected?: boolean;\n  rejected?: boolean;\n  /** @format int32 */\n  tvdbId?: number;\n  rejections?: string[] | null;\n  /** @format date-time */\n  publishDate?: string;\n  commentUrl?: string | null;\n  downloadUrl?: string | null;\n  infoUrl?: string | null;\n  episodeRequested?: boolean;\n  downloadAllowed?: boolean;\n  /** @format int32 */\n  releaseWeight?: number;\n  customFormats?: CustomFormatResource[] | null;\n  /** @format int32 */\n  customFormatScore?: number;\n  magnetUrl?: string | null;\n  infoHash?: string | null;\n  /** @format int32 */\n  seeders?: number | null;\n  /** @format int32 */\n  leechers?: number | null;\n  protocol?: DownloadProtocol;\n  isDaily?: boolean;\n  /** @format int32 */\n  seriesId?: number | null;\n  /** @format int32 */\n  episodeId?: number | null;\n  episodeIds?: number[] | null;\n  /** @format int32 */\n  downloadClientId?: number | null;\n  shouldOverride?: boolean | null;\n}\n\nexport interface RemotePathMappingResource {\n  /** @format int32 */\n  id?: number;\n  host?: string | null;\n  remotePath?: string | null;\n  localPath?: string | null;\n}\n\nexport interface RenameEpisodeResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  seriesId?: number;\n  /** @format int32 */\n  seasonNumber?: number;\n  releaseDates?: string[] | null;\n  /** @format int32 */\n  episodeFileId?: number;\n  existingPath?: string | null;\n  newPath?: string | null;\n}\n\nexport interface Revision {\n  /** @format int32 */\n  version?: number;\n  /** @format int32 */\n  real?: number;\n  isRepack?: boolean;\n}\n\nexport interface RootFolderResource {\n  /** @format int32 */\n  id?: number;\n  path?: string | null;\n  accessible?: boolean;\n  /** @format int64 */\n  freeSpace?: number | null;\n  unmappedFolders?: UnmappedFolder[] | null;\n}\n\nexport interface SeasonPassResource {\n  series?: SeasonPassSeriesResource[] | null;\n  monitoringOptions?: MonitoringOptions;\n}\n\nexport interface SeasonPassSeriesResource {\n  /** @format int32 */\n  id?: number;\n  monitored?: boolean | null;\n  seasons?: SeasonResource[] | null;\n}\n\nexport interface SeasonResource {\n  /** @format int32 */\n  seasonNumber?: number;\n  monitored?: boolean;\n  statistics?: SeasonStatisticsResource;\n  images?: MediaCover[] | null;\n}\n\nexport interface SeasonStatisticsResource {\n  /** @format date-time */\n  nextAiring?: string | null;\n  /** @format date-time */\n  previousAiring?: string | null;\n  /** @format int32 */\n  episodeFileCount?: number;\n  /** @format int32 */\n  episodeCount?: number;\n  /** @format int32 */\n  totalEpisodeCount?: number;\n  /** @format int64 */\n  sizeOnDisk?: number;\n  releaseGroups?: string[] | null;\n  /** @format double */\n  percentOfEpisodes?: number;\n}\n\nexport interface SelectOption {\n  /** @format int32 */\n  value?: number;\n  name?: string | null;\n  /** @format int32 */\n  order?: number;\n  hint?: string | null;\n}\n\nexport interface SeriesEditorResource {\n  seriesIds?: number[] | null;\n  monitored?: boolean | null;\n  /** @format int32 */\n  qualityProfileId?: number | null;\n  rootFolderPath?: string | null;\n  tags?: number[] | null;\n  applyTags?: ApplyTags;\n  moveFiles?: boolean;\n  deleteFiles?: boolean;\n  addImportListExclusion?: boolean;\n}\n\nexport interface SeriesResource {\n  /** @format int32 */\n  id?: number;\n  title?: string | null;\n  sortTitle?: string | null;\n  status?: SeriesStatusType;\n  ended?: boolean;\n  profileName?: string | null;\n  overview?: string | null;\n  /** @format date-time */\n  nextAiring?: string | null;\n  /** @format date-time */\n  previousAiring?: string | null;\n  network?: string | null;\n  images?: MediaCover[] | null;\n  originalLanguage?: Language;\n  remotePoster?: string | null;\n  seasons?: SeasonResource[] | null;\n  /** @format int32 */\n  year?: number;\n  path?: string | null;\n  /** @format int32 */\n  qualityProfileId?: number;\n  monitored?: boolean;\n  useSceneNumbering?: boolean;\n  /** @format int32 */\n  runtime?: number;\n  /** @format int32 */\n  tvdbId?: number;\n  /** @format date-time */\n  firstAired?: string | null;\n  cleanTitle?: string | null;\n  titleSlug?: string | null;\n  rootFolderPath?: string | null;\n  folder?: string | null;\n  certification?: string | null;\n  genres?: string[] | null;\n  /** @uniqueItems true */\n  tags?: number[] | null;\n  /** @format date-time */\n  added?: string;\n  addOptions?: AddSeriesOptions;\n  ratings?: Ratings;\n  statistics?: SeriesStatisticsResource;\n  episodesChanged?: boolean | null;\n}\n\nexport interface SeriesStatisticsResource {\n  /** @format int32 */\n  seasonCount?: number;\n  /** @format int32 */\n  episodeFileCount?: number;\n  /** @format int32 */\n  episodeCount?: number;\n  /** @format int32 */\n  totalEpisodeCount?: number;\n  /** @format int64 */\n  sizeOnDisk?: number;\n  releaseGroups?: string[] | null;\n  /** @format double */\n  percentOfEpisodes?: number;\n}\n\nexport interface SeriesTitleInfo {\n  title?: string | null;\n  titleWithoutYear?: string | null;\n  /** @format int32 */\n  year?: number;\n  allTitles?: string[] | null;\n}\n\nexport interface SystemResource {\n  appName?: string | null;\n  instanceName?: string | null;\n  version?: string | null;\n  /** @format date-time */\n  buildTime?: string;\n  isDebug?: boolean;\n  isProduction?: boolean;\n  isAdmin?: boolean;\n  isUserInteractive?: boolean;\n  startupPath?: string | null;\n  appData?: string | null;\n  osName?: string | null;\n  osVersion?: string | null;\n  isNetCore?: boolean;\n  isLinux?: boolean;\n  isOsx?: boolean;\n  isWindows?: boolean;\n  isDocker?: boolean;\n  mode?: RuntimeMode;\n  branch?: string | null;\n  authentication?: AuthenticationType;\n  sqliteVersion?: Version;\n  /** @format int32 */\n  migrationVersion?: number;\n  urlBase?: string | null;\n  runtimeVersion?: Version;\n  runtimeName?: string | null;\n  /** @format date-time */\n  startTime?: string;\n  packageVersion?: string | null;\n  packageAuthor?: string | null;\n  packageUpdateMechanism?: UpdateMechanism;\n  packageUpdateMechanismMessage?: string | null;\n  databaseVersion?: Version;\n  databaseType?: DatabaseType;\n}\n\nexport interface TagDetailsResource {\n  /** @format int32 */\n  id?: number;\n  label?: string | null;\n  delayProfileIds?: number[] | null;\n  importListIds?: number[] | null;\n  notificationIds?: number[] | null;\n  restrictionIds?: number[] | null;\n  indexerIds?: number[] | null;\n  downloadClientIds?: number[] | null;\n  autoTagIds?: number[] | null;\n  seriesIds?: number[] | null;\n}\n\nexport interface TagResource {\n  /** @format int32 */\n  id?: number;\n  label?: string | null;\n}\n\nexport interface TaskResource {\n  /** @format int32 */\n  id?: number;\n  name?: string | null;\n  taskName?: string | null;\n  /** @format int32 */\n  interval?: number;\n  /** @format date-time */\n  lastExecution?: string;\n  /** @format date-time */\n  lastStartTime?: string;\n  /** @format date-time */\n  nextExecution?: string;\n  lastDuration?: TimeSpan;\n}\n\nexport interface TimeSpan {\n  /** @format int64 */\n  ticks?: number;\n  /** @format int32 */\n  days?: number;\n  /** @format int32 */\n  hours?: number;\n  /** @format int32 */\n  milliseconds?: number;\n  /** @format int32 */\n  minutes?: number;\n  /** @format int32 */\n  seconds?: number;\n  /** @format double */\n  totalDays?: number;\n  /** @format double */\n  totalHours?: number;\n  /** @format double */\n  totalMilliseconds?: number;\n  /** @format double */\n  totalMinutes?: number;\n  /** @format double */\n  totalSeconds?: number;\n}\n\nexport interface TrackedDownloadStatusMessage {\n  title?: string | null;\n  messages?: string[] | null;\n}\n\nexport interface UiConfigResource {\n  /** @format int32 */\n  id?: number;\n  /** @format int32 */\n  firstDayOfWeek?: number;\n  calendarWeekColumnHeader?: string | null;\n  shortDateFormat?: string | null;\n  longDateFormat?: string | null;\n  timeFormat?: string | null;\n  showRelativeDates?: boolean;\n  enableColorImpairedMode?: boolean;\n  theme?: string | null;\n  /** @format int32 */\n  uiLanguage?: number;\n}\n\nexport interface UnmappedFolder {\n  name?: string | null;\n  path?: string | null;\n  relativePath?: string | null;\n}\n\nexport interface UpdateChanges {\n  new?: string[] | null;\n  fixed?: string[] | null;\n}\n\nexport interface UpdateResource {\n  /** @format int32 */\n  id?: number;\n  version?: Version;\n  branch?: string | null;\n  /** @format date-time */\n  releaseDate?: string;\n  fileName?: string | null;\n  url?: string | null;\n  installed?: boolean;\n  /** @format date-time */\n  installedOn?: string | null;\n  installable?: boolean;\n  latest?: boolean;\n  changes?: UpdateChanges;\n  hash?: string | null;\n}\n\nexport interface Version {\n  /** @format int32 */\n  major?: number;\n  /** @format int32 */\n  minor?: number;\n  /** @format int32 */\n  build?: number;\n  /** @format int32 */\n  revision?: number;\n  /** @format int32 */\n  majorRevision?: number;\n  /** @format int32 */\n  minorRevision?: number;\n}\n"
  },
  {
    "path": "src/cache.ts",
    "content": "import {\n  MergedCustomFormatResource,\n  MergedQualityDefinitionResource,\n  MergedQualityProfileResource,\n  MergedTagResource,\n} from \"./types/merged.types\";\nimport { ArrClientLanguageResource } from \"./clients/unified-client\";\nimport { logger } from \"./logger\";\nimport type { DownloadClientResource } from \"./types/download-client.types\";\n\nexport class ServerCache {\n  private cache: Record<string, any> = {};\n  private _qd: MergedQualityDefinitionResource[];\n  private _qp: MergedQualityProfileResource[];\n  private _cf: MergedCustomFormatResource[];\n  private _tags: MergedTagResource[] = [];\n  private _languages: ArrClientLanguageResource[];\n  private _downloadClientSchema: DownloadClientResource[] | null = null;\n\n  constructor(\n    qd: MergedQualityDefinitionResource[],\n    qp: MergedQualityProfileResource[],\n    cf: MergedCustomFormatResource[],\n    languages: ArrClientLanguageResource[],\n  ) {\n    this._qd = qd;\n    this._qp = qp;\n    this._cf = cf;\n    this._languages = languages;\n  }\n\n  public get<T>(key: string): T | null {\n    return this.cache[key] ?? null;\n  }\n\n  public set<T>(key: string, value: T): void {\n    this.cache[key] = value;\n  }\n\n  public get qd() {\n    return this._qd;\n  }\n\n  public set qd(newQd: MergedQualityDefinitionResource[]) {\n    if (newQd == null || newQd.length <= 0) {\n      // Empty should never happen\n      logger.debug(`No QualityDefinition received from server.`);\n      throw new Error(\"No QualityDefinitions received from server.\");\n    }\n    this._qd = newQd;\n  }\n\n  public get qp() {\n    return this._qp;\n  }\n\n  public set qp(newQp: MergedQualityProfileResource[]) {\n    if (newQp == null || newQp.length <= 0) {\n      logger.debug(`No QualityProfiles received from server.`);\n    }\n    this._qp = newQp;\n  }\n\n  public get cf() {\n    return this._cf;\n  }\n\n  public set cf(newCf: MergedCustomFormatResource[]) {\n    if (newCf == null || newCf.length <= 0) {\n      logger.debug(`No CustomFormats received from server.`);\n    }\n    this._cf = newCf;\n  }\n\n  public get languages() {\n    return this._languages;\n  }\n\n  public set languages(newLanguages: ArrClientLanguageResource[]) {\n    if (newLanguages == null || newLanguages.length <= 0) {\n      // Empty should never happen\n      logger.debug(`No Languages received from server.`);\n      throw new Error(\"No Languages received from server.\");\n    }\n    this._languages = newLanguages;\n  }\n\n  public get tags() {\n    return this._tags;\n  }\n  public set tags(newTags: MergedTagResource[]) {\n    if (newTags == null || newTags.length <= 0) {\n      logger.debug(`No Tags received from server.`);\n    }\n    this._tags = newTags;\n  }\n\n  public getDownloadClientSchema(): DownloadClientResource[] | null {\n    return this._downloadClientSchema;\n  }\n\n  public setDownloadClientSchema(schema: DownloadClientResource[]): void {\n    this._downloadClientSchema = schema;\n  }\n}\n"
  },
  {
    "path": "src/clients/lidarr-client.ts",
    "content": "import { KyHttpClient } from \"../ky-client\";\nimport { Api } from \"../__generated__/lidarr/Api\";\nimport {\n  CustomFormatResource,\n  DownloadClientConfigResource,\n  LanguageResource,\n  MetadataProfileResource,\n  QualityDefinitionResource,\n  QualityProfileResource,\n  RemotePathMappingResource,\n  UiConfigResource,\n} from \"../__generated__/lidarr/data-contracts\";\nimport { logger } from \"../logger\";\nimport type { DownloadClientResource } from \"../types/download-client.types\";\nimport { IArrClient, logConnectionError, validateClientParams } from \"./unified-client\";\n\nexport class LidarrClient implements IArrClient<QualityProfileResource, QualityDefinitionResource, CustomFormatResource, LanguageResource> {\n  private api!: Api<unknown>;\n\n  constructor(baseUrl: string, apiKey: string) {\n    this.initialize(baseUrl, apiKey);\n  }\n\n  private initialize(baseUrl: string, apiKey: string) {\n    validateClientParams(baseUrl, apiKey, \"LIDARR\");\n\n    const httpClient = new KyHttpClient({\n      headers: {\n        \"X-Api-Key\": apiKey,\n      },\n      prefixUrl: baseUrl,\n    });\n\n    this.api = new Api(httpClient);\n  }\n\n  async getLanguages() {\n    return this.api.v1LanguageList();\n  }\n\n  // Quality Management\n  getQualityDefinitions() {\n    return this.api.v1QualitydefinitionList();\n  }\n\n  async updateQualityDefinitions(definitions: QualityDefinitionResource[]) {\n    await this.api.v1QualitydefinitionUpdateUpdate(definitions);\n    return this.api.v1QualitydefinitionList();\n  }\n\n  // Quality Profiles\n  getQualityProfiles() {\n    return this.api.v1QualityprofileList();\n  }\n\n  createQualityProfile(profile: QualityProfileResource) {\n    return this.api.v1QualityprofileCreate(profile);\n  }\n\n  updateQualityProfile(id: string, profile: QualityProfileResource) {\n    return this.api.v1QualityprofileUpdate(id, profile);\n  }\n\n  deleteQualityProfile(id: string): Promise<void> {\n    return this.api.v1QualityprofileDelete(Number(id));\n  }\n\n  // Custom Formats\n  getCustomFormats() {\n    return this.api.v1CustomformatList();\n  }\n\n  createCustomFormat(format: CustomFormatResource) {\n    return this.api.v1CustomformatCreate(format);\n  }\n\n  updateCustomFormat(id: string, format: CustomFormatResource) {\n    return this.api.v1CustomformatUpdate(id, format);\n  }\n\n  deleteCustomFormat(id: string) {\n    return this.api.v1CustomformatDelete(+id);\n  }\n\n  // Metadata Profiles\n  getMetadataProfiles() {\n    return this.api.v1MetadataprofileList();\n  }\n\n  getMetadataProfileSchema() {\n    return this.api.v1MetadataprofileSchemaList();\n  }\n\n  createMetadataProfile(profile: MetadataProfileResource) {\n    return this.api.v1MetadataprofileCreate(profile);\n  }\n\n  updateMetadataProfile(id: string, profile: MetadataProfileResource) {\n    return this.api.v1MetadataprofileUpdate(id, profile);\n  }\n\n  deleteMetadataProfile(id: string) {\n    return this.api.v1MetadataprofileDelete(Number(id));\n  }\n\n  async getNaming() {\n    return this.api.v1ConfigNamingList();\n  }\n\n  async updateNaming(id: string, data: any) {\n    return this.api.v1ConfigNamingUpdate(id, data);\n  }\n\n  async getMediamanagement() {\n    return this.api.v1ConfigMediamanagementList();\n  }\n\n  async updateMediamanagement(id: string, data: any) {\n    return this.api.v1ConfigMediamanagementUpdate(id, data);\n  }\n\n  async getUiConfig(): Promise<UiConfigResource> {\n    return this.api.v1ConfigUiList();\n  }\n\n  async updateUiConfig(id: string, data: UiConfigResource): Promise<UiConfigResource> {\n    return this.api.v1ConfigUiUpdate(id, data);\n  }\n\n  async getRootfolders() {\n    return this.api.v1RootfolderList();\n  }\n\n  async addRootFolder(data: any) {\n    return this.api.v1RootfolderCreate(data);\n  }\n\n  async updateRootFolder(id: string, data: any) {\n    return this.api.v1RootfolderUpdate(id, data);\n  }\n\n  async deleteRootFolder(id: string) {\n    return this.api.v1RootfolderDelete(+id);\n  }\n\n  // Delay Profiles\n  async getDelayProfiles() {\n    return this.api.v1DelayprofileList();\n  }\n\n  async createDelayProfile(profile: any) {\n    return this.api.v1DelayprofileCreate(profile);\n  }\n\n  async updateDelayProfile(id: string, data: any) {\n    return this.api.v1DelayprofileUpdate(id, data);\n  }\n\n  async deleteDelayProfile(id: string) {\n    return this.api.v1DelayprofileDelete(+id);\n  }\n\n  async getTags() {\n    return this.api.v1TagList();\n  }\n\n  async createTag(tag: any) {\n    return this.api.v1TagCreate(tag);\n  }\n\n  // Download Clients\n  async getDownloadClientSchema(): Promise<DownloadClientResource[]> {\n    return this.api.v1DownloadclientSchemaList();\n  }\n\n  async getDownloadClients(): Promise<DownloadClientResource[]> {\n    return this.api.v1DownloadclientList();\n  }\n\n  async createDownloadClient(client: DownloadClientResource): Promise<DownloadClientResource> {\n    return this.api.v1DownloadclientCreate(client);\n  }\n\n  async updateDownloadClient(id: string, client: DownloadClientResource): Promise<DownloadClientResource> {\n    return this.api.v1DownloadclientUpdate(+id, client);\n  }\n\n  async deleteDownloadClient(id: string): Promise<void> {\n    return this.api.v1DownloadclientDelete(+id);\n  }\n\n  async testDownloadClient(client: DownloadClientResource): Promise<any> {\n    return this.api.v1DownloadclientTestCreate(client);\n  }\n\n  // Download Client Configuration\n  async getDownloadClientConfig(): Promise<DownloadClientConfigResource> {\n    return this.api.v1ConfigDownloadclientList();\n  }\n\n  async updateDownloadClientConfig(id: string, config: DownloadClientConfigResource): Promise<DownloadClientConfigResource> {\n    return this.api.v1ConfigDownloadclientUpdate(id, config);\n  }\n\n  // Remote Path Mappings\n  async getRemotePathMappings(): Promise<RemotePathMappingResource[]> {\n    return this.api.v1RemotepathmappingList();\n  }\n\n  async createRemotePathMapping(mapping: RemotePathMappingResource): Promise<RemotePathMappingResource> {\n    return this.api.v1RemotepathmappingCreate(mapping);\n  }\n\n  async updateRemotePathMapping(id: string, mapping: RemotePathMappingResource): Promise<RemotePathMappingResource> {\n    return this.api.v1RemotepathmappingUpdate(id, mapping);\n  }\n\n  async deleteRemotePathMapping(id: string): Promise<void> {\n    return this.api.v1RemotepathmappingDelete(+id);\n  }\n\n  // System/Health Check\n  getSystemStatus() {\n    return this.api.v1SystemStatusList();\n  }\n\n  async testConnection() {\n    try {\n      await this.api.v1HealthList();\n    } catch (error) {\n      const message = logConnectionError(error, \"LIDARR\");\n      logger.error(message);\n\n      return false;\n    }\n\n    return true;\n  }\n}\n"
  },
  {
    "path": "src/clients/radarr-client.ts",
    "content": "import { KyHttpClient } from \"../ky-client\";\nimport { Api } from \"../__generated__/radarr/Api\";\nimport {\n  CustomFormatResource,\n  DownloadClientConfigResource,\n  LanguageResource,\n  QualityDefinitionResource,\n  QualityProfileResource,\n  RemotePathMappingResource,\n  UiConfigResource,\n} from \"../__generated__/radarr/data-contracts\";\nimport { logger } from \"../logger\";\nimport type { DownloadClientResource } from \"../types/download-client.types\";\nimport { cloneWithJSON } from \"../util\";\nimport { IArrClient, logConnectionError, validateClientParams } from \"./unified-client\";\n\nexport class RadarrClient implements IArrClient<QualityProfileResource, QualityDefinitionResource, CustomFormatResource, LanguageResource> {\n  private api!: Api<unknown>;\n  private languageMap: Map<string, LanguageResource> = new Map();\n\n  constructor(baseUrl: string, apiKey: string) {\n    this.initialize(baseUrl, apiKey);\n  }\n\n  private initialize(baseUrl: string, apiKey: string) {\n    validateClientParams(baseUrl, apiKey, \"RADARR\");\n\n    const httpClient = new KyHttpClient({\n      headers: {\n        \"X-Api-Key\": apiKey,\n      },\n      prefixUrl: baseUrl,\n    });\n\n    this.api = new Api(httpClient);\n  }\n\n  async getLanguages() {\n    return this.api.v3LanguageList();\n  }\n\n  // Quality Management\n  getQualityDefinitions() {\n    return this.api.v3QualitydefinitionList();\n  }\n\n  async updateQualityDefinitions(definitions: QualityDefinitionResource[]) {\n    await this.api.v3QualitydefinitionUpdateUpdate(definitions);\n    this.api.v3LanguageList();\n    return this.getQualityDefinitions();\n  }\n\n  // Quality Profiles\n  getQualityProfiles(): Promise<QualityProfileResource[]> {\n    return this.api.v3QualityprofileList();\n  }\n\n  async createQualityProfile(profile: QualityProfileResource): Promise<QualityProfileResource> {\n    const cloned = cloneWithJSON(profile);\n\n    if (this.languageMap.size <= 0) {\n      const languages = await this.getLanguages();\n      this.languageMap = new Map(languages.map((i) => [i.name!, i]));\n    }\n\n    if (profile.language == null) {\n      cloned.language = this.languageMap.get(\"Any\");\n    }\n\n    return this.api.v3QualityprofileCreate(cloned);\n  }\n\n  updateQualityProfile(id: string, profile: QualityProfileResource): Promise<QualityProfileResource> {\n    return this.api.v3QualityprofileUpdate(id, profile);\n  }\n\n  deleteQualityProfile(id: string): Promise<void> {\n    return this.api.v3QualityprofileDelete(Number(id));\n  }\n\n  // Custom Formats\n  getCustomFormats() {\n    return this.api.v3CustomformatList();\n  }\n\n  createCustomFormat(format: CustomFormatResource) {\n    return this.api.v3CustomformatCreate(format);\n  }\n\n  updateCustomFormat(id: string, format: CustomFormatResource) {\n    return this.api.v3CustomformatUpdate(id, format);\n  }\n\n  deleteCustomFormat(id: string) {\n    return this.api.v3CustomformatDelete(+id);\n  }\n\n  async getNaming() {\n    return this.api.v3ConfigNamingList();\n  }\n\n  async updateNaming(id: string, data: any) {\n    return this.api.v3ConfigNamingUpdate(id, data);\n  }\n\n  async getMediamanagement() {\n    return this.api.v3ConfigMediamanagementList();\n  }\n\n  async updateMediamanagement(id: string, data: any) {\n    return this.api.v3ConfigMediamanagementUpdate(id, data);\n  }\n\n  async getUiConfig(): Promise<UiConfigResource> {\n    return this.api.v3ConfigUiList();\n  }\n\n  async updateUiConfig(id: string, data: UiConfigResource): Promise<UiConfigResource> {\n    return this.api.v3ConfigUiUpdate(id, data);\n  }\n\n  async getRootfolders() {\n    return this.api.v3RootfolderList();\n  }\n\n  async addRootFolder(data: any) {\n    return this.api.v3RootfolderCreate(data);\n  }\n\n  async updateRootFolder(id: string, data: any) {\n    throw new Error(\"Radarr does not support updating root folders\");\n  }\n\n  async deleteRootFolder(id: string) {\n    return this.api.v3RootfolderDelete(+id);\n  }\n\n  // Delay Profiles\n  async getDelayProfiles() {\n    return this.api.v3DelayprofileList();\n  }\n\n  async createDelayProfile(profile: any) {\n    return this.api.v3DelayprofileCreate(profile);\n  }\n\n  async updateDelayProfile(id: string, data: any) {\n    return this.api.v3DelayprofileUpdate(id, data);\n  }\n\n  async deleteDelayProfile(id: string) {\n    return this.api.v3DelayprofileDelete(+id);\n  }\n\n  async getTags() {\n    return this.api.v3TagList();\n  }\n\n  async createTag(tag: any) {\n    return this.api.v3TagCreate(tag);\n  }\n\n  // Download Clients\n  async getDownloadClientSchema(): Promise<DownloadClientResource[]> {\n    return this.api.v3DownloadclientSchemaList();\n  }\n\n  async getDownloadClients(): Promise<DownloadClientResource[]> {\n    return this.api.v3DownloadclientList();\n  }\n\n  async createDownloadClient(client: DownloadClientResource): Promise<DownloadClientResource> {\n    return this.api.v3DownloadclientCreate(client);\n  }\n\n  async updateDownloadClient(id: string, client: DownloadClientResource): Promise<DownloadClientResource> {\n    return this.api.v3DownloadclientUpdate(+id, client);\n  }\n\n  async deleteDownloadClient(id: string): Promise<void> {\n    return this.api.v3DownloadclientDelete(+id);\n  }\n\n  async testDownloadClient(client: DownloadClientResource): Promise<any> {\n    return this.api.v3DownloadclientTestCreate(client);\n  }\n\n  // Download Client Configuration\n  async getDownloadClientConfig(): Promise<DownloadClientConfigResource> {\n    return this.api.v3ConfigDownloadclientList();\n  }\n\n  async updateDownloadClientConfig(id: string, config: DownloadClientConfigResource): Promise<DownloadClientConfigResource> {\n    return this.api.v3ConfigDownloadclientUpdate(id, config);\n  }\n\n  // Remote Path Mappings\n  async getRemotePathMappings(): Promise<RemotePathMappingResource[]> {\n    return this.api.v3RemotepathmappingList();\n  }\n\n  async createRemotePathMapping(mapping: RemotePathMappingResource): Promise<RemotePathMappingResource> {\n    return this.api.v3RemotepathmappingCreate(mapping);\n  }\n\n  async updateRemotePathMapping(id: string, mapping: RemotePathMappingResource): Promise<RemotePathMappingResource> {\n    return this.api.v3RemotepathmappingUpdate(id, mapping);\n  }\n\n  async deleteRemotePathMapping(id: string): Promise<void> {\n    return this.api.v3RemotepathmappingDelete(+id);\n  }\n\n  // System/Health Check\n  getSystemStatus() {\n    return this.api.v3SystemStatusList();\n  }\n\n  async testConnection() {\n    try {\n      await this.api.v3HealthList();\n    } catch (error) {\n      const message = logConnectionError(error, \"RADARR\");\n      logger.error(message);\n      return false;\n    }\n\n    return true;\n  }\n}\n"
  },
  {
    "path": "src/clients/readarr-client.ts",
    "content": "import { KyHttpClient } from \"../ky-client\";\nimport { Api } from \"../__generated__/readarr/Api\";\nimport {\n  CustomFormatResource,\n  DownloadClientConfigResource,\n  LanguageResource,\n  MetadataProfileResource,\n  QualityDefinitionResource,\n  QualityProfileResource,\n  RemotePathMappingResource,\n  UiConfigResource,\n} from \"../__generated__/readarr/data-contracts\";\nimport { logger } from \"../logger\";\nimport type { DownloadClientResource } from \"../types/download-client.types\";\nimport { IArrClient, logConnectionError, validateClientParams } from \"./unified-client\";\n\nexport class ReadarrClient implements IArrClient<\n  QualityProfileResource,\n  QualityDefinitionResource,\n  CustomFormatResource,\n  LanguageResource\n> {\n  private api!: Api<unknown>;\n\n  constructor(baseUrl: string, apiKey: string) {\n    this.initialize(baseUrl, apiKey);\n  }\n\n  private initialize(baseUrl: string, apiKey: string) {\n    validateClientParams(baseUrl, apiKey, \"READARR\");\n\n    const httpClient = new KyHttpClient({\n      headers: {\n        \"X-Api-Key\": apiKey,\n      },\n      prefixUrl: baseUrl,\n    });\n\n    this.api = new Api(httpClient);\n  }\n\n  async getLanguages() {\n    return this.api.v1LanguageList();\n  }\n\n  // Quality Management\n  getQualityDefinitions() {\n    return this.api.v1QualitydefinitionList();\n  }\n\n  async updateQualityDefinitions(definitions: QualityDefinitionResource[]) {\n    await this.api.v1QualitydefinitionUpdateUpdate(definitions);\n    return this.api.v1QualitydefinitionList();\n  }\n\n  // Quality Profiles\n  getQualityProfiles() {\n    return this.api.v1QualityprofileList();\n  }\n\n  createQualityProfile(profile: QualityProfileResource) {\n    return this.api.v1QualityprofileCreate(profile);\n  }\n\n  updateQualityProfile(id: string, profile: QualityProfileResource) {\n    return this.api.v1QualityprofileUpdate(id, profile);\n  }\n\n  deleteQualityProfile(id: string): Promise<void> {\n    return this.api.v1QualityprofileDelete(Number(id));\n  }\n\n  // Custom Formats\n  getCustomFormats() {\n    return this.api.v1CustomformatList();\n  }\n\n  createCustomFormat(format: CustomFormatResource) {\n    return this.api.v1CustomformatCreate(format);\n  }\n\n  updateCustomFormat(id: string, format: CustomFormatResource) {\n    return this.api.v1CustomformatUpdate(id, format);\n  }\n\n  deleteCustomFormat(id: string) {\n    return this.api.v1CustomformatDelete(+id);\n  }\n\n  // Metadata Profiles\n  async getMetadataProfiles() {\n    return this.api.v1MetadataprofileList();\n  }\n\n  async createMetadataProfile(profile: MetadataProfileResource) {\n    return this.api.v1MetadataprofileCreate(profile);\n  }\n\n  async updateMetadataProfile(id: string, profile: MetadataProfileResource) {\n    return this.api.v1MetadataprofileUpdate(id, profile);\n  }\n\n  async deleteMetadataProfile(id: string) {\n    return this.api.v1MetadataprofileDelete(Number(id));\n  }\n\n  async getNaming() {\n    return this.api.v1ConfigNamingList();\n  }\n\n  async updateNaming(id: string, data: any) {\n    return this.api.v1ConfigNamingUpdate(id, data);\n  }\n\n  async getMediamanagement() {\n    return this.api.v1ConfigMediamanagementList();\n  }\n\n  async updateMediamanagement(id: string, data: any) {\n    return this.api.v1ConfigMediamanagementUpdate(id, data);\n  }\n\n  async getUiConfig(): Promise<UiConfigResource> {\n    return this.api.v1ConfigUiList();\n  }\n\n  async updateUiConfig(id: string, data: UiConfigResource): Promise<UiConfigResource> {\n    return this.api.v1ConfigUiUpdate(id, data);\n  }\n\n  async getRootfolders() {\n    return this.api.v1RootfolderList();\n  }\n\n  async addRootFolder(data: any) {\n    return this.api.v1RootfolderCreate(data);\n  }\n\n  async updateRootFolder(id: string, data: any) {\n    return this.api.v1RootfolderUpdate(id, data);\n  }\n\n  async deleteRootFolder(id: string) {\n    return this.api.v1RootfolderDelete(+id);\n  }\n\n  // Delay Profiles\n  async getDelayProfiles() {\n    return this.api.v1DelayprofileList();\n  }\n\n  async createDelayProfile(profile: any) {\n    return this.api.v1DelayprofileCreate(profile);\n  }\n\n  async updateDelayProfile(id: string, data: any) {\n    return this.api.v1DelayprofileUpdate(id, data);\n  }\n\n  async deleteDelayProfile(id: string) {\n    return this.api.v1DelayprofileDelete(+id);\n  }\n\n  async getTags() {\n    return this.api.v1TagList();\n  }\n\n  async createTag(tag: any) {\n    return this.api.v1TagCreate(tag);\n  }\n\n  // Download Clients\n  async getDownloadClientSchema(): Promise<DownloadClientResource[]> {\n    return this.api.v1DownloadclientSchemaList();\n  }\n\n  async getDownloadClients(): Promise<DownloadClientResource[]> {\n    return this.api.v1DownloadclientList();\n  }\n\n  async createDownloadClient(client: DownloadClientResource): Promise<DownloadClientResource> {\n    return this.api.v1DownloadclientCreate(client);\n  }\n\n  // Note: Readarr's v1 API expects string for update but number for delete\n  async updateDownloadClient(id: string, client: DownloadClientResource): Promise<DownloadClientResource> {\n    return this.api.v1DownloadclientUpdate(id, client);\n  }\n\n  async deleteDownloadClient(id: string): Promise<void> {\n    return this.api.v1DownloadclientDelete(+id);\n  }\n\n  async testDownloadClient(client: DownloadClientResource): Promise<any> {\n    return this.api.v1DownloadclientTestCreate(client);\n  }\n\n  // Download Client Configuration\n  async getDownloadClientConfig(): Promise<DownloadClientConfigResource> {\n    return this.api.v1ConfigDownloadclientList();\n  }\n\n  async updateDownloadClientConfig(id: string, config: DownloadClientConfigResource): Promise<DownloadClientConfigResource> {\n    return this.api.v1ConfigDownloadclientUpdate(id, config);\n  }\n\n  // Remote Path Mappings\n  async getRemotePathMappings(): Promise<RemotePathMappingResource[]> {\n    return this.api.v1RemotepathmappingList();\n  }\n\n  async createRemotePathMapping(mapping: RemotePathMappingResource): Promise<RemotePathMappingResource> {\n    return this.api.v1RemotepathmappingCreate(mapping);\n  }\n\n  async updateRemotePathMapping(id: string, mapping: RemotePathMappingResource): Promise<RemotePathMappingResource> {\n    return this.api.v1RemotepathmappingUpdate(id, mapping);\n  }\n\n  async deleteRemotePathMapping(id: string): Promise<void> {\n    return this.api.v1RemotepathmappingDelete(+id);\n  }\n\n  // System/Health Check\n  getSystemStatus() {\n    return this.api.v1SystemStatusList();\n  }\n\n  async testConnection() {\n    try {\n      await this.api.v1HealthList();\n    } catch (error) {\n      const message = logConnectionError(error, \"READARR\");\n      logger.error(message);\n      return false;\n    }\n\n    return true;\n  }\n}\n"
  },
  {
    "path": "src/clients/sonarr-client.ts",
    "content": "import { KyHttpClient } from \"../ky-client\";\nimport { Api } from \"../__generated__/sonarr/Api\";\nimport {\n  CustomFormatResource,\n  DownloadClientConfigResource,\n  LanguageResource,\n  QualityDefinitionResource,\n  QualityProfileResource,\n  RemotePathMappingResource,\n  UiConfigResource,\n} from \"../__generated__/sonarr/data-contracts\";\nimport { logger } from \"../logger\";\nimport type { DownloadClientResource } from \"../types/download-client.types\";\nimport { IArrClient, logConnectionError, validateClientParams } from \"./unified-client\";\n\nexport type SonarrQualityProfileResource = {\n  id?: number;\n  name?: string;\n  // Add other common properties that all quality profiles share\n};\n\nexport class SonarrClient implements IArrClient<QualityProfileResource, QualityDefinitionResource, CustomFormatResource, LanguageResource> {\n  private api!: Api<unknown>;\n\n  constructor(baseUrl: string, apiKey: string) {\n    this.initialize(baseUrl, apiKey);\n  }\n\n  private initialize(baseUrl: string, apiKey: string) {\n    validateClientParams(baseUrl, apiKey, \"SONARR\");\n\n    const httpClient = new KyHttpClient({\n      headers: {\n        \"X-Api-Key\": apiKey,\n      },\n      prefixUrl: baseUrl,\n    });\n\n    this.api = new Api(httpClient);\n  }\n\n  async getLanguages() {\n    return this.api.v3LanguageList();\n  }\n\n  // Quality Management\n  getQualityDefinitions() {\n    return this.api.v3QualitydefinitionList();\n  }\n\n  async updateQualityDefinitions(definitions: QualityDefinitionResource[]) {\n    this.api.v3QualitydefinitionUpdateUpdate(definitions);\n    return this.api.v3QualitydefinitionList();\n  }\n\n  // Quality Profiles\n  getQualityProfiles() {\n    return this.api.v3QualityprofileList();\n  }\n\n  createQualityProfile(profile: SonarrQualityProfileResource) {\n    return this.api.v3QualityprofileCreate(profile);\n  }\n\n  updateQualityProfile(id: string, profile: SonarrQualityProfileResource) {\n    return this.api.v3QualityprofileUpdate(id, profile);\n  }\n\n  deleteQualityProfile(id: string): Promise<void> {\n    return this.api.v3QualityprofileDelete(Number(id));\n  }\n\n  // Custom Formats\n  getCustomFormats() {\n    return this.api.v3CustomformatList();\n  }\n\n  createCustomFormat(format: CustomFormatResource) {\n    return this.api.v3CustomformatCreate(format);\n  }\n\n  updateCustomFormat(id: string, format: CustomFormatResource) {\n    return this.api.v3CustomformatUpdate(id, format);\n  }\n\n  deleteCustomFormat(id: string) {\n    return this.api.v3CustomformatDelete(+id);\n  }\n\n  async getNaming() {\n    return this.api.v3ConfigNamingList();\n  }\n\n  async updateNaming(id: string, data: any) {\n    return this.api.v3ConfigNamingUpdate(id, data);\n  }\n\n  async getMediamanagement() {\n    return this.api.v3ConfigMediamanagementList();\n  }\n\n  async updateMediamanagement(id: string, data: any) {\n    return this.api.v3ConfigMediamanagementUpdate(id, data);\n  }\n\n  async getUiConfig(): Promise<UiConfigResource> {\n    return this.api.v3ConfigUiList();\n  }\n\n  async updateUiConfig(id: string, data: UiConfigResource): Promise<UiConfigResource> {\n    return this.api.v3ConfigUiUpdate(id, data);\n  }\n\n  async getRootfolders() {\n    return this.api.v3RootfolderList();\n  }\n\n  async addRootFolder(data: any) {\n    return this.api.v3RootfolderCreate(data);\n  }\n\n  async updateRootFolder(id: string, data: any) {\n    throw new Error(\"Sonarr does not support updating root folders\");\n  }\n\n  async deleteRootFolder(id: string) {\n    return this.api.v3RootfolderDelete(+id);\n  }\n\n  // Delay Profiles\n  async getDelayProfiles() {\n    return this.api.v3DelayprofileList();\n  }\n\n  async createDelayProfile(profile: any) {\n    return this.api.v3DelayprofileCreate(profile);\n  }\n\n  async updateDelayProfile(id: string, data: any) {\n    return this.api.v3DelayprofileUpdate(id, data);\n  }\n\n  async deleteDelayProfile(id: string) {\n    return this.api.v3DelayprofileDelete(+id);\n  }\n\n  async getTags() {\n    return this.api.v3TagList();\n  }\n\n  async createTag(tag: any) {\n    return this.api.v3TagCreate(tag);\n  }\n\n  // Download Clients\n  async getDownloadClientSchema(): Promise<DownloadClientResource[]> {\n    return this.api.v3DownloadclientSchemaList();\n  }\n\n  async getDownloadClients(): Promise<DownloadClientResource[]> {\n    return this.api.v3DownloadclientList();\n  }\n\n  async createDownloadClient(client: DownloadClientResource): Promise<DownloadClientResource> {\n    return this.api.v3DownloadclientCreate(client);\n  }\n\n  async updateDownloadClient(id: string, client: DownloadClientResource): Promise<DownloadClientResource> {\n    return this.api.v3DownloadclientUpdate(+id, client);\n  }\n\n  async deleteDownloadClient(id: string): Promise<void> {\n    return this.api.v3DownloadclientDelete(+id);\n  }\n\n  async testDownloadClient(client: DownloadClientResource): Promise<any> {\n    return this.api.v3DownloadclientTestCreate(client);\n  }\n\n  // Download Client Configuration\n  async getDownloadClientConfig(): Promise<DownloadClientConfigResource> {\n    return this.api.v3ConfigDownloadclientList();\n  }\n\n  async updateDownloadClientConfig(id: string, config: DownloadClientConfigResource): Promise<DownloadClientConfigResource> {\n    return this.api.v3ConfigDownloadclientUpdate(id, config);\n  }\n\n  // Remote Path Mappings\n  async getRemotePathMappings(): Promise<RemotePathMappingResource[]> {\n    return this.api.v3RemotepathmappingList();\n  }\n\n  async createRemotePathMapping(mapping: RemotePathMappingResource): Promise<RemotePathMappingResource> {\n    return this.api.v3RemotepathmappingCreate(mapping);\n  }\n\n  async updateRemotePathMapping(id: string, mapping: RemotePathMappingResource): Promise<RemotePathMappingResource> {\n    return this.api.v3RemotepathmappingUpdate(id, mapping);\n  }\n\n  async deleteRemotePathMapping(id: string): Promise<void> {\n    return this.api.v3RemotepathmappingDelete(+id);\n  }\n\n  // System/Health Check\n  getSystemStatus() {\n    return this.api.v3SystemStatusList();\n  }\n\n  async testConnection() {\n    try {\n      await this.api.v3HealthList();\n    } catch (error) {\n      const message = logConnectionError(error, \"SONARR\");\n      logger.error(message);\n      return false;\n    }\n\n    return true;\n  }\n}\n"
  },
  {
    "path": "src/clients/unified-client.ts",
    "content": "import { MergedCustomFormatResource, MergedQualityDefinitionResource, MergedQualityProfileResource } from \"../types/merged.types\";\nimport { logger } from \"../logger\";\nimport { ArrType } from \"../types/common.types\";\nimport type { DownloadClientResource } from \"../types/download-client.types\";\nimport { LidarrClient } from \"./lidarr-client\";\nimport { RadarrClient } from \"./radarr-client\";\nimport { ReadarrClient } from \"./readarr-client\";\nimport { SonarrClient } from \"./sonarr-client\";\nimport { WhisparrClient } from \"./whisparr-client\";\n\nlet unifiedClient: UnifiedClient | undefined;\n\nexport const unsetApi = () => {\n  unifiedClient = undefined;\n};\n\nexport const getUnifiedClient = (): UnifiedClient => {\n  if (!unifiedClient) {\n    throw new Error(\"Please configure API first.\");\n  }\n  return unifiedClient;\n};\n\n/**\n * Type map that maps ArrType to its corresponding client type\n */\ntype ArrTypeToClient = {\n  RADARR: RadarrClient;\n  SONARR: SonarrClient;\n  LIDARR: LidarrClient;\n  READARR: ReadarrClient;\n  WHISPARR: WhisparrClient;\n};\n\n/**\n * Get the underlying specific client instance by arrType\n * This bypasses the unified client wrapper and returns the actual RadarrClient, SonarrClient, etc.\n * Type-safe: TypeScript will infer the correct client type based on the arrType parameter.\n * @throws Error if the requested arrType doesn't match the configured client type\n */\nexport function getSpecificClient<T extends ArrType>(arrType: T): ArrTypeToClient[T] {\n  const client = getUnifiedClient();\n  // Validate that the requested type matches the configured client type\n  if (client.type !== arrType) {\n    throw new Error(\n      `Type mismatch: requested ${arrType} but client is configured for ${client.type}. Ensure configureApi is called with the correct arrType.`,\n    );\n  }\n  return (client as any).api;\n}\n\nexport const validateClientParams = (url: string, apiKey: string, arrType: ArrType) => {\n  const arrLabel = arrType.toLowerCase();\n\n  if (!url) {\n    const message = `URL not correctly configured for ${arrLabel} API!`;\n    logger.error(message);\n    throw new Error(message);\n  }\n  if (!apiKey) {\n    const message = `API Key not correctly configured for ${arrLabel} API!`;\n    logger.error(message);\n    throw new Error(message);\n  }\n};\n\nexport const logConnectionError = (error: any, arrType: ArrType) => {\n  const arrLabel = arrType.toLowerCase();\n  const errorParts = createConnectionErrorParts(error);\n\n  if (errorParts.length > 0) {\n    const [friendlyMessage, structuredMessage] = errorParts;\n    const bestMessage = structuredMessage || friendlyMessage;\n    return `Connection to ${arrLabel} API failed: ${bestMessage}`;\n  }\n\n  // Fallback to original logic if createConnectionErrorParts returns empty\n  const causeError = error?.cause?.message || error?.cause?.errors?.map((e: any) => e.message).join(\";\") || undefined;\n  const errorMessage = (error.message && `Message: ${error.message}`) || \"\";\n  const causeMessage = (causeError && `- Cause: ${causeError}`) || \"\";\n\n  if (error.response) {\n    return `Unable to retrieve data from ${arrLabel} API. Server responded with status code ${error.response.status}: ${error.response.statusText}. Please check the API server status or your request parameters.`;\n  } else {\n    return `An unexpected error occurred while setting up the ${arrLabel} request: ${errorMessage} ${causeMessage}. Please try again.`;\n  }\n};\n\n/**\n * Create detailed error parts for connection testing and error reporting\n * Returns an array of error messages with structured details and user-friendly messages\n */\nexport const createConnectionErrorParts = (error: unknown): string[] => {\n  const errorMessage = error instanceof Error ? error.message : String(error);\n  const httpError = error as any;\n\n  const status = httpError?.response?.status;\n  const data = httpError?.response?.data;\n\n  let structuredDetail: string | undefined;\n\n  if (data) {\n    if (typeof data === \"string\") {\n      structuredDetail = data;\n    } else if (typeof data === \"object\" && data !== null) {\n      const message = (data as any).message ?? (data as any).error;\n      const errors = Array.isArray((data as any).errors)\n        ? (data as any).errors.map((e: any) => e.errorMessage ?? e.message ?? String(e)).join(\"; \")\n        : undefined;\n\n      structuredDetail = [message, errors].filter(Boolean).join(\" - \") || undefined;\n    }\n  }\n\n  const statusPrefix = status ? `HTTP ${status}` : \"Connection test failed\";\n  const structuredMessage = structuredDetail ? `${statusPrefix}: ${structuredDetail}` : statusPrefix;\n\n  // Common connection error patterns with user-friendly messages\n  let friendly: string | undefined;\n  if (errorMessage.includes(\"connection refused\") || errorMessage.includes(\"ECONNREFUSED\")) {\n    friendly = \"Connection refused - check host and port\";\n  } else if (errorMessage.includes(\"timeout\") || errorMessage.includes(\"ETIMEDOUT\")) {\n    friendly = \"Connection timeout - check network connectivity\";\n  } else if (errorMessage.includes(\"unauthorized\") || errorMessage.includes(\"401\")) {\n    friendly = \"Authentication failed - check username/password/API key\";\n  } else if (errorMessage.includes(\"not found\") || errorMessage.includes(\"404\")) {\n    friendly = \"Endpoint not found - check URL base path\";\n  }\n\n  return [friendly, structuredMessage, errorMessage].filter((part, index, self) => part && self.indexOf(part) === index) as string[];\n};\n\nexport const configureApi = async (type: ArrType, baseUrl: string, apiKey: string) => {\n  unsetApi();\n\n  unifiedClient = new UnifiedClient(type, baseUrl, apiKey);\n  let connectionSuccessful = false;\n\n  try {\n    connectionSuccessful = await unifiedClient.testConnection();\n  } catch (error: any) {\n    logger.error(`Unhandled connection error.`);\n    throw error;\n  }\n\n  if (!connectionSuccessful) {\n    throw new Error(`Could not connect to client: ${type} - ${baseUrl}`);\n  }\n\n  return unifiedClient;\n};\n\nexport type ArrClientCustomFormat = {\n  id?: number;\n};\n\nexport type ArrClientQualityDefinition = {\n  id?: number;\n};\n\nexport type ArrClientQualityProfile = {\n  id?: number;\n  name?: string | null;\n  // Add other common properties that all quality profiles share\n};\n\nexport type ArrClientLanguageResource = {\n  id?: number;\n  name?: string | null;\n  nameLower?: string | null;\n};\n\nexport interface IArrClient<\n  QP extends ArrClientQualityProfile = MergedQualityProfileResource,\n  QD extends ArrClientQualityDefinition = MergedQualityDefinitionResource,\n  CF extends ArrClientCustomFormat = MergedCustomFormatResource,\n  L extends ArrClientLanguageResource = ArrClientLanguageResource,\n> {\n  // Quality Management\n  getQualityDefinitions(): Promise<QD[]>;\n  updateQualityDefinitions(definitions: QD[]): Promise<QD[]>;\n\n  // Quality Profiles\n  getQualityProfiles(): Promise<QP[]>;\n  createQualityProfile(profile: QP): Promise<QP>;\n  updateQualityProfile(id: string, profile: QP): Promise<QP>;\n  deleteQualityProfile(id: string): Promise<void>;\n\n  // Custom Formats\n  getCustomFormats(): Promise<CF[]>;\n  createCustomFormat(format: CF): Promise<CF>;\n  updateCustomFormat(id: string, format: CF): Promise<CF>;\n  deleteCustomFormat(id: string): Promise<void>;\n\n  getNaming(): Promise<any>;\n  updateNaming(id: string, data: any): Promise<any>;\n\n  getMediamanagement(): Promise<any>;\n  updateMediamanagement(id: string, data: any): Promise<any>;\n\n  getRootfolders(): Promise<any>;\n  addRootFolder(data: any): Promise<any>;\n  updateRootFolder(id: string, data: any): Promise<any>;\n  deleteRootFolder(id: string): Promise<any>;\n\n  getLanguages(): Promise<L[]>;\n\n  // Delay Profiles\n  getDelayProfiles(): Promise<any>;\n  createDelayProfile(profile: any): Promise<any>;\n  updateDelayProfile(id: string, data: any): Promise<any>;\n  deleteDelayProfile(id: string): Promise<any>;\n\n  // Tags\n  getTags(): Promise<any>;\n  createTag(tag: any): Promise<any>;\n  // deleteTag(id: string): Promise<void>;\n  // updateTag(id: string, tag: any): Promise<any>;\n\n  // Download Clients\n  getDownloadClientSchema(): Promise<DownloadClientResource[]>;\n  getDownloadClients(): Promise<DownloadClientResource[]>;\n  createDownloadClient(client: DownloadClientResource): Promise<DownloadClientResource>;\n  updateDownloadClient(id: string, client: DownloadClientResource): Promise<DownloadClientResource>;\n  deleteDownloadClient(id: string): Promise<void>;\n  testDownloadClient(client: DownloadClientResource): Promise<any>;\n\n  // System/Health Check\n  getSystemStatus(): Promise<any>;\n  testConnection(): Promise<boolean>;\n}\n\nexport class UnifiedClient implements IArrClient {\n  private api!: IArrClient;\n  readonly type: ArrType;\n\n  constructor(type: ArrType, baseUrl: string, apiKey: string) {\n    this.type = type;\n    switch (type) {\n      case \"SONARR\":\n        this.api = new SonarrClient(baseUrl, apiKey);\n        break;\n      case \"RADARR\":\n        this.api = new RadarrClient(baseUrl, apiKey);\n        break;\n      case \"READARR\":\n        this.api = new ReadarrClient(baseUrl, apiKey);\n        break;\n      case \"WHISPARR\":\n        this.api = new WhisparrClient(baseUrl, apiKey);\n        break;\n      case \"LIDARR\":\n        this.api = new LidarrClient(baseUrl, apiKey);\n        break;\n      default:\n        throw new Error(`Invalid API type: ${type}`);\n    }\n  }\n\n  async getLanguages() {\n    return this.api.getLanguages();\n  }\n\n  async getQualityDefinitions() {\n    return await this.api.getQualityDefinitions();\n  }\n\n  async updateQualityDefinitions(definitions: MergedQualityDefinitionResource[]) {\n    return await this.api.updateQualityDefinitions(definitions);\n  }\n\n  async createQualityProfile(profile: MergedQualityProfileResource) {\n    return await this.api.createQualityProfile(profile);\n  }\n\n  async getQualityProfiles() {\n    return await this.api.getQualityProfiles();\n  }\n\n  async updateQualityProfile(id: string, profile: MergedQualityProfileResource) {\n    return await this.api.updateQualityProfile(id, profile);\n  }\n\n  async deleteQualityProfile(id: string) {\n    return await this.api.deleteQualityProfile(id);\n  }\n\n  async getCustomFormats() {\n    return await this.api.getCustomFormats();\n  }\n\n  async createCustomFormat(format: MergedCustomFormatResource) {\n    return await this.api.createCustomFormat(format);\n  }\n\n  async updateCustomFormat(id: string, format: MergedCustomFormatResource) {\n    return await this.api.updateCustomFormat(id, format);\n  }\n\n  async deleteCustomFormat(id: string) {\n    return await this.api.deleteCustomFormat(id);\n  }\n\n  async getNaming() {\n    return this.api.getNaming();\n  }\n\n  async updateNaming(id: string, data: any) {\n    return this.api.updateNaming(id, data);\n  }\n\n  async getMediamanagement() {\n    return this.api.getMediamanagement();\n  }\n\n  async updateMediamanagement(id: string, data: any) {\n    return this.api.updateMediamanagement(id, data);\n  }\n\n  async getRootfolders() {\n    return this.api.getRootfolders();\n  }\n\n  async addRootFolder(data: any) {\n    return this.api.addRootFolder(data);\n  }\n\n  async updateRootFolder(id: string, data: any) {\n    return this.api.updateRootFolder(id, data);\n  }\n\n  async deleteRootFolder(id: string) {\n    return this.api.deleteRootFolder(id);\n  }\n\n  async getDelayProfiles() {\n    return this.api.getDelayProfiles();\n  }\n\n  async createDelayProfile(profile: any) {\n    return this.api.createDelayProfile(profile);\n  }\n\n  async updateDelayProfile(id: string, data: any) {\n    return this.api.updateDelayProfile(id, data);\n  }\n\n  async deleteDelayProfile(id: string) {\n    return this.api.deleteDelayProfile(id);\n  }\n\n  async getTags() {\n    return this.api.getTags();\n  }\n\n  async createTag(tag: any) {\n    return this.api.createTag(tag);\n  }\n\n  async getDownloadClientSchema() {\n    return this.api.getDownloadClientSchema();\n  }\n\n  async getDownloadClients() {\n    return this.api.getDownloadClients();\n  }\n\n  async createDownloadClient(client: DownloadClientResource): Promise<DownloadClientResource> {\n    return this.api.createDownloadClient(client);\n  }\n\n  async updateDownloadClient(id: string, client: DownloadClientResource): Promise<DownloadClientResource> {\n    return this.api.updateDownloadClient(id, client);\n  }\n\n  async deleteDownloadClient(id: string): Promise<void> {\n    return this.api.deleteDownloadClient(id);\n  }\n\n  async testDownloadClient(client: DownloadClientResource): Promise<any> {\n    return this.api.testDownloadClient(client);\n  }\n\n  async getSystemStatus() {\n    return await this.api.getSystemStatus();\n  }\n\n  async testConnection() {\n    return await this.api.testConnection();\n  }\n}\n"
  },
  {
    "path": "src/clients/whisparr-client.ts",
    "content": "import { KyHttpClient } from \"../ky-client\";\nimport { Api } from \"../__generated__/whisparr/Api\";\nimport {\n  CustomFormatResource,\n  DownloadClientConfigResource,\n  LanguageResource,\n  QualityDefinitionResource,\n  QualityProfileResource,\n  RemotePathMappingResource,\n  UiConfigResource,\n} from \"../__generated__/whisparr/data-contracts\";\nimport { logger } from \"../logger\";\nimport type { DownloadClientResource } from \"../types/download-client.types\";\nimport { cloneWithJSON } from \"../util\";\nimport { IArrClient, logConnectionError, validateClientParams } from \"./unified-client\";\n\n/**\n * Overwrite wrong types for now\n */\ndeclare module \"../__generated__/whisparr/data-contracts\" {\n  export interface QualityProfileResource {\n    language?: Language;\n  }\n}\n\nexport class WhisparrClient implements IArrClient<\n  QualityProfileResource,\n  QualityDefinitionResource,\n  CustomFormatResource,\n  LanguageResource\n> {\n  private api!: Api<unknown>;\n  private languageMap: Map<string, LanguageResource> = new Map();\n\n  constructor(baseUrl: string, apiKey: string) {\n    this.initialize(baseUrl, apiKey);\n  }\n\n  private initialize(baseUrl: string, apiKey: string) {\n    validateClientParams(baseUrl, apiKey, \"WHISPARR\");\n\n    const httpClient = new KyHttpClient({\n      headers: {\n        \"X-Api-Key\": apiKey,\n      },\n      prefixUrl: baseUrl,\n    });\n\n    this.api = new Api(httpClient);\n  }\n\n  async getLanguages() {\n    return this.api.v3LanguageList();\n  }\n\n  // Quality Management\n  getQualityDefinitions() {\n    return this.api.v3QualitydefinitionList();\n  }\n\n  async updateQualityDefinitions(definitions: QualityDefinitionResource[]) {\n    await this.api.v3QualitydefinitionUpdateUpdate(definitions);\n    return this.api.v3QualitydefinitionList();\n  }\n\n  // Quality Profiles\n  getQualityProfiles() {\n    return this.api.v3QualityprofileList();\n  }\n\n  async createQualityProfile(profile: QualityProfileResource): Promise<QualityProfileResource> {\n    const cloned = cloneWithJSON(profile);\n\n    if (this.languageMap.size <= 0) {\n      const languages = await this.getLanguages();\n      this.languageMap = new Map(languages.map((i) => [i.name!, i]));\n    }\n\n    if (profile.language == null) {\n      cloned.language = this.languageMap.get(\"Any\");\n    }\n\n    return this.api.v3QualityprofileCreate(cloned);\n  }\n\n  updateQualityProfile(id: string, profile: QualityProfileResource) {\n    return this.api.v3QualityprofileUpdate(id, profile);\n  }\n\n  deleteQualityProfile(id: string): Promise<void> {\n    return this.api.v3QualityprofileDelete(Number(id));\n  }\n\n  // Custom Formats\n  getCustomFormats() {\n    return this.api.v3CustomformatList();\n  }\n\n  createCustomFormat(format: CustomFormatResource) {\n    return this.api.v3CustomformatCreate(format);\n  }\n\n  updateCustomFormat(id: string, format: CustomFormatResource) {\n    return this.api.v3CustomformatUpdate(id, format);\n  }\n\n  deleteCustomFormat(id: string) {\n    return this.api.v3CustomformatDelete(+id);\n  }\n\n  async getNaming() {\n    return this.api.v3ConfigNamingList();\n  }\n\n  async updateNaming(id: string, data: any) {\n    return this.api.v3ConfigNamingUpdate(id, data);\n  }\n\n  async getMediamanagement() {\n    return this.api.v3ConfigMediamanagementList();\n  }\n\n  async updateMediamanagement(id: string, data: any) {\n    return this.api.v3ConfigMediamanagementUpdate(id, data);\n  }\n\n  async getUiConfig(): Promise<UiConfigResource> {\n    return this.api.v3ConfigUiList();\n  }\n\n  async updateUiConfig(id: string, data: UiConfigResource): Promise<UiConfigResource> {\n    return this.api.v3ConfigUiUpdate(id, data);\n  }\n\n  async getRootfolders() {\n    return this.api.v3RootfolderList();\n  }\n\n  async addRootFolder(data: any) {\n    return this.api.v3RootfolderCreate(data);\n  }\n\n  async updateRootFolder(id: string, data: any) {\n    throw new Error(\"Whisparr does not support updating root folders\");\n  }\n\n  async deleteRootFolder(id: string) {\n    return this.api.v3RootfolderDelete(+id);\n  }\n\n  // Delay Profiles\n  async getDelayProfiles() {\n    return this.api.v3DelayprofileList();\n  }\n\n  async createDelayProfile(profile: any) {\n    return this.api.v3DelayprofileCreate(profile);\n  }\n\n  async updateDelayProfile(id: string, data: any) {\n    return this.api.v3DelayprofileUpdate(id, data);\n  }\n\n  async deleteDelayProfile(id: string) {\n    return this.api.v3DelayprofileDelete(+id);\n  }\n\n  async getTags() {\n    return this.api.v3TagList();\n  }\n\n  async createTag(tag: any) {\n    return this.api.v3TagCreate(tag);\n  }\n\n  // Download Clients\n  async getDownloadClientSchema(): Promise<DownloadClientResource[]> {\n    return this.api.v3DownloadclientSchemaList();\n  }\n\n  async getDownloadClients(): Promise<DownloadClientResource[]> {\n    return this.api.v3DownloadclientList();\n  }\n\n  async createDownloadClient(client: DownloadClientResource): Promise<DownloadClientResource> {\n    return this.api.v3DownloadclientCreate(client);\n  }\n\n  // Note: Whisparr's v3 API expects string for update but number for delete\n  async updateDownloadClient(id: string, client: DownloadClientResource): Promise<DownloadClientResource> {\n    return this.api.v3DownloadclientUpdate(id, client);\n  }\n\n  async deleteDownloadClient(id: string): Promise<void> {\n    return this.api.v3DownloadclientDelete(+id);\n  }\n\n  async testDownloadClient(client: DownloadClientResource): Promise<any> {\n    return this.api.v3DownloadclientTestCreate(client);\n  }\n\n  // Download Client Configuration\n  async getDownloadClientConfig(): Promise<DownloadClientConfigResource> {\n    return this.api.v3ConfigDownloadclientList();\n  }\n\n  async updateDownloadClientConfig(id: string, config: DownloadClientConfigResource): Promise<DownloadClientConfigResource> {\n    return this.api.v3ConfigDownloadclientUpdate(id, config);\n  }\n\n  // Remote Path Mappings\n  async getRemotePathMappings(): Promise<RemotePathMappingResource[]> {\n    return this.api.v3RemotepathmappingList();\n  }\n\n  async createRemotePathMapping(mapping: RemotePathMappingResource): Promise<RemotePathMappingResource> {\n    return this.api.v3RemotepathmappingCreate(mapping);\n  }\n\n  async updateRemotePathMapping(id: string, mapping: RemotePathMappingResource): Promise<RemotePathMappingResource> {\n    return this.api.v3RemotepathmappingUpdate(id, mapping);\n  }\n\n  async deleteRemotePathMapping(id: string): Promise<void> {\n    return this.api.v3RemotepathmappingDelete(+id);\n  }\n\n  // System/Health Check\n  getSystemStatus() {\n    return this.api.v3SystemStatusList();\n  }\n\n  async testConnection() {\n    try {\n      await this.api.v3HealthList();\n    } catch (error) {\n      const message = logConnectionError(error, \"WHISPARR\");\n      logger.error(message);\n      return false;\n    }\n\n    return true;\n  }\n}\n"
  },
  {
    "path": "src/config.test.ts",
    "content": "import { beforeEach, describe, expect, test, vi } from \"vitest\";\nimport yaml from \"yaml\";\nimport {\n  getSecrets,\n  isTrashQualityDefinition,\n  mergeConfigsAndTemplates,\n  readConfigRaw,\n  resetSecretsCache,\n  transformConfig,\n} from \"./config\";\nimport * as env from \"./env\";\nimport * as localImporter from \"./local-importer\";\nimport * as reclarrImporter from \"./recyclarr-importer\";\nimport * as trashGuide from \"./trash-guide\";\nimport { MappedTemplates } from \"./types/common.types\";\nimport {\n  ConfigQualityProfile,\n  ConfigQualityProfileItem,\n  InputConfigArrInstance,\n  InputConfigCustomFormat,\n  InputConfigSchema,\n} from \"./types/config.types\";\nimport { TrashQP, TrashQualityDefinition } from \"./types/trashguide.types\";\nimport { cloneWithJSON } from \"./util\";\n\n// Mock ky for URL template tests\nconst mockKyGet = vi.hoisted(() => vi.fn());\nvi.mock(\"ky\", () => {\n  const mockKy = vi.fn() as any;\n  mockKy.get = mockKyGet;\n  return {\n    default: mockKy,\n  };\n});\n\n// Mock fast-glob for secrets tests\nconst mockFastGlobSync = vi.hoisted(() => vi.fn());\nvi.mock(\"fast-glob\", () => {\n  return {\n    default: {\n      sync: mockFastGlobSync,\n    },\n  };\n});\n\n// Mock fs for secrets tests - must be hoisted before imports\nconst mockExistsSync = vi.hoisted(() => vi.fn());\nconst mockReadFileSync = vi.hoisted(() => vi.fn());\nvi.mock(\"node:fs\", async () => {\n  const actual = await vi.importActual<typeof import(\"node:fs\")>(\"node:fs\");\n  return {\n    ...actual,\n    existsSync: mockExistsSync,\n    readFileSync: mockReadFileSync,\n  };\n});\n\ndescribe(\"transformConfig\", async () => {\n  const config: InputConfigSchema = yaml.parse(`\nsonarr:\n  instance1:\n    base_url: http://sonarr:8989\n    api_key: test\n\n    quality_profiles:\n      - name: Remux-2160p - Anime\n        quality_sort: top\n        score_set: anime-sonarr\n        upgrade:\n          allowed: true\n          until_quality: SINGLE_STAGE\n          until_score: 5000000\n        qualities:\n          - name: SINGLE_STAGE\n            enabled: true\n            qualities:\n              - Bluray-2160p\n              - SDTV\n          - name: Unknown\n            enabled: false\n    custom_formats:\n      - trash_ids:\n          - custom-german-dl\n          - custom-german-dl-2\n        assign_scores_to:\n          - name: Remux-2160p - Anime\nradarr: {}\n`);\n\n  test(\"should transform without error\", async () => {\n    const transformed = transformConfig(config);\n    expect(transformed).not.toBeNull();\n  });\n\n  test(\"should transform without error - quality_profiles instead of assign_scores_to\", async () => {\n    const cloned = cloneWithJSON(config);\n    const instance = cloned.sonarr![\"instance1\"]!;\n    const cF = instance.custom_formats![0];\n\n    instance.custom_formats![0] = { ...cF, quality_profiles: cF?.assign_scores_to, assign_scores_to: undefined };\n\n    const transformed = transformConfig(config);\n    expect(transformed).not.toBeNull();\n  });\n});\n\ndescribe(\"mergeConfigsAndTemplates\", () => {\n  beforeEach(() => {\n    // Reset mocks before each test\n    vi.resetAllMocks();\n  });\n\n  test(\"should merge valid configurations and templates\", async () => {\n    const fromConfig: ConfigQualityProfileItem[] = [{ name: \"HDTV-1080p\", enabled: false }];\n\n    const profile: ConfigQualityProfile = {\n      name: \"profile1\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"sort\",\n      upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n      score_set: \"default\",\n    };\n\n    const profile2 = cloneWithJSON(profile);\n    profile2.name = \"profile2\";\n\n    const profile4 = cloneWithJSON(profile);\n    profile4.name = \"profile4\";\n\n    const trashProfile: TrashQP = {\n      trash_id: \"profile3\",\n      name: \"profile3\",\n      trash_score_set: \"default\",\n      upgradeAllowed: true,\n      cutoff: \"Merged QPs\",\n      minFormatScore: 0,\n      cutoffFormatScore: 25000,\n      language: \"Any\",\n      items: [\n        { name: \"Unknown\", allowed: false },\n        { name: \"WORKPRINT\", allowed: false },\n        { name: \"CAM\", allowed: false },\n        { name: \"TELESYNC\", allowed: false },\n        { name: \"TELECINE\", allowed: false },\n        { name: \"REGIONAL\", allowed: false },\n        { name: \"DVDSCR\", allowed: false },\n        { name: \"SDTV\", allowed: false },\n        { name: \"DVD\", allowed: false },\n        { name: \"DVD-R\", allowed: false },\n        {\n          name: \"WEB 480p\",\n          allowed: false,\n          items: [\"WEBDL-480p\", \"WEBRip-480p\"],\n        },\n        { name: \"Bluray-480p\", allowed: false },\n        { name: \"Bluray-576p\", allowed: false },\n        { name: \"HDTV-720p\", allowed: false },\n        { name: \"HDTV-1080p\", allowed: false },\n        { name: \"Remux-1080p\", allowed: false },\n        { name: \"HDTV-2160p\", allowed: false },\n        {\n          name: \"WEB 2160p\",\n          allowed: false,\n          items: [\"WEBDL-2160p\", \"WEBRip-2160p\"],\n        },\n        { name: \"Bluray-2160p\", allowed: false },\n        { name: \"Remux-2160p\", allowed: false },\n        { name: \"BR-DISK\", allowed: false },\n        { name: \"Raw-HD\", allowed: false },\n        {\n          name: \"Merged QPs\",\n          allowed: true,\n          items: [\"WEBRip-720p\", \"WEBDL-720p\", \"Bluray-720p\", \"WEBDL-1080p\", \"WEBRip-1080p\", \"Bluray-1080p\"],\n        },\n      ],\n      formatItems: {\n        Test: \"cf1\",\n      },\n    };\n\n    // Mock template data\n    const recyclarrTemplates: Map<string, MappedTemplates> = new Map<string, MappedTemplates>([\n      [\"template1\", { custom_formats: [{ trash_ids: [\"cf1\"] }], quality_profiles: [profile] }],\n    ]);\n    const localTemplates: Map<string, MappedTemplates> = new Map<string, MappedTemplates>([\n      [\"template2\", { custom_formats: [{ trash_ids: [\"cf2\"] }], quality_profiles: [profile2] }],\n    ]);\n    const trashTemplates: Map<string, TrashQP> = new Map<string, TrashQP>([[\"template3\", trashProfile]]);\n\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(localTemplates);\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(trashTemplates));\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n\n    const inputConfig: InputConfigArrInstance = {\n      include: [\n        { template: \"template1\", source: \"RECYCLARR\" },\n        { template: \"template3\", source: \"TRASH\" },\n      ],\n      custom_formats: [{ trash_ids: [\"cf4\"] }],\n      quality_profiles: [profile4],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n\n    const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n\n    expect(result.config.custom_formats.length).toBe(2); // was 3, now 2 after deduplication\n    expect(result.config.quality_profiles.length).toBe(3);\n  });\n\n  test(\"should handle missing templates gracefully\", async () => {\n    const fromConfig: ConfigQualityProfileItem[] = [{ name: \"HDTV-1080p\", enabled: false }];\n\n    const profile: ConfigQualityProfile = {\n      name: \"profile1\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"sort\",\n      upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n      score_set: \"default\",\n    };\n\n    // Mock template data\n    const recyclarrTemplates: Map<string, MappedTemplates> = new Map<string, MappedTemplates>([\n      [\"template1\", { custom_formats: [{ trash_ids: [\"cf1\"] }], quality_profiles: [profile] }],\n    ]);\n    const localTemplates: Map<string, MappedTemplates> = new Map<string, MappedTemplates>();\n    const trashTemplates: Map<string, TrashQP> = new Map<string, TrashQP>();\n\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(localTemplates);\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(trashTemplates));\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n\n    const inputConfig: InputConfigArrInstance = {\n      include: [\n        { template: \"unknown\", source: \"RECYCLARR\" },\n        { template: \"unknown-2\", source: \"TRASH\" },\n      ],\n      custom_formats: [],\n      quality_profiles: [],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n\n    const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n\n    expect(result.config.custom_formats.length).toBe(0);\n    expect(result.config.quality_profiles.length).toBe(0);\n  });\n\n  test(\"should prioritize config values over template values\", async () => {\n    const fromConfig: ConfigQualityProfileItem[] = [{ name: \"HDTV-1080p\", enabled: false }];\n\n    const profile: ConfigQualityProfile = {\n      name: \"profile1\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"sort\",\n      upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n      score_set: \"default\",\n    };\n\n    const profile4 = cloneWithJSON(profile);\n    profile4.name = \"profile1\";\n    profile4.min_format_score = 5;\n\n    // Mock template data\n    const recyclarrTemplates: Map<string, MappedTemplates> = new Map<string, MappedTemplates>([\n      [\"template1\", { custom_formats: [{ trash_ids: [\"cf1\"] }], quality_profiles: [profile] }],\n    ]);\n\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n\n    const inputConfig: InputConfigArrInstance = {\n      include: [{ template: \"template1\", source: \"RECYCLARR\" }],\n      custom_formats: [{ trash_ids: [\"cf4\"] }],\n      quality_profiles: [profile4],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n\n    const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n\n    expect(result.config.custom_formats.length).toBe(2);\n    expect(result.config.quality_profiles.length).toBe(1);\n    expect(result.config.quality_profiles[0]!.min_format_score).toBe(5);\n  });\n\n  test(\"should handle recursive includes gracefully (not supported)\", async () => {\n    const fromConfig: ConfigQualityProfileItem[] = [{ name: \"HDTV-1080p\", enabled: false }];\n\n    const profile: ConfigQualityProfile = {\n      name: \"profile1\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"sort\",\n      upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n      score_set: \"default\",\n    };\n\n    const profile4 = cloneWithJSON(profile);\n    profile4.name = \"profile1\";\n    profile4.min_format_score = 5;\n\n    // Mock template data\n    const recyclarrTemplates: Map<string, MappedTemplates> = new Map<string, MappedTemplates>([\n      [\n        \"template1\",\n        {\n          include: [{ template: \"template2\", source: \"RECYCLARR\" }],\n        },\n      ],\n      [\"template2\", { custom_formats: [{ trash_ids: [\"cf1\"] }], quality_profiles: [profile] }],\n    ]);\n\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n\n    const inputConfig: InputConfigArrInstance = {\n      include: [{ template: \"template1\", source: \"RECYCLARR\" }],\n      custom_formats: [],\n      quality_profiles: [],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n\n    const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n\n    expect(result.config.custom_formats.length).toBe(0);\n    expect(result.config.quality_profiles.length).toBe(0);\n  });\n\n  test(\"should throw error for invalid input configuration\", async () => {\n    await expect(mergeConfigsAndTemplates({}, null as any, \"SONARR\")).rejects.toThrow();\n  });\n\n  test(\"should integrate URL templates with config merging\", async () => {\n    const fromConfig: ConfigQualityProfileItem[] = [{ name: \"HDTV-1080p\", enabled: false }];\n\n    const profile: ConfigQualityProfile = {\n      name: \"url-profile\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"sort\",\n      upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n      score_set: \"default\",\n    };\n\n    const urlTemplate: MappedTemplates = {\n      custom_formats: [{ trash_ids: [\"cf-url\"], assign_scores_to: [{ name: \"url-profile\" }] }],\n      quality_profiles: [profile],\n    };\n\n    mockKyGet.mockResolvedValue({\n      text: async () => yaml.stringify(urlTemplate),\n    });\n\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(new Map());\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n\n    const inputConfig: InputConfigArrInstance = {\n      include: [{ template: \"https://example.com/template.yml\" }],\n      custom_formats: [],\n      quality_profiles: [],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n\n    const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n\n    expect(mockKyGet).toHaveBeenCalledWith(\"https://example.com/template.yml\", { timeout: 30000 });\n    expect(result.config.custom_formats.length).toBe(1);\n    expect(result.config.custom_formats[0]!.trash_ids).toEqual([\"cf-url\"]);\n    expect(result.config.quality_profiles.length).toBe(1);\n    expect(result.config.quality_profiles[0]!.name).toBe(\"url-profile\");\n  });\n\n  test(\"should integrate TRASH URL templates with config merging\", async () => {\n    const trashTemplate: TrashQP = {\n      trash_id: \"test-trash-id\",\n      name: \"TRASH Profile\",\n      trash_score_set: \"default\",\n      upgradeAllowed: true,\n      cutoff: \"HDTV-1080p\",\n      minFormatScore: 0,\n      cutoffFormatScore: 1000,\n      items: [{ name: \"HDTV-1080p\", allowed: true }],\n      formatItems: {},\n    };\n\n    mockKyGet.mockResolvedValue({\n      text: async () => JSON.stringify(trashTemplate),\n    });\n\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(new Map());\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n\n    const inputConfig: InputConfigArrInstance = {\n      include: [{ template: \"https://example.com/trash-template.json\", source: \"TRASH\" }],\n      custom_formats: [],\n      quality_profiles: [],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n\n    const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n\n    expect(mockKyGet).toHaveBeenCalledWith(\"https://example.com/trash-template.json\", { timeout: 30000 });\n    expect(result.config.quality_profiles.length).toBe(1);\n    expect(result.config.quality_profiles[0]!.name).toBe(\"TRASH Profile\");\n  });\n\n  test(\"should auto-detect QD JSON from TRASH URL and apply it to quality_definition\", async () => {\n    const qdTemplate: TrashQualityDefinition = {\n      trash_id: \"aed34b9f60ee115dfa7918b742336277\",\n      type: \"movie\",\n      qualities: [\n        { quality: \"HDTV-720p\", min: 10, preferred: 50, max: 67 },\n        { quality: \"Bluray-1080p\", min: 17, preferred: 140, max: 400 },\n      ],\n    };\n\n    mockKyGet.mockResolvedValue({\n      text: async () => JSON.stringify(qdTemplate),\n    });\n\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(new Map());\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n    vi.spyOn(trashGuide, \"loadAllQDsFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n\n    const inputConfig: InputConfigArrInstance = {\n      include: [{ template: \"https://example.com/quality-size.json\", source: \"TRASH\" }],\n      custom_formats: [],\n      quality_profiles: [],\n      api_key: \"test\",\n      base_url: \"http://radarr:7878\",\n    };\n\n    const result = await mergeConfigsAndTemplates({}, inputConfig, \"RADARR\");\n\n    expect(mockKyGet).toHaveBeenCalledWith(\"https://example.com/quality-size.json\", { timeout: 30000 });\n    expect(result.config.quality_definition).toBeDefined();\n    expect(result.config.quality_definition?.qualities).toHaveLength(2);\n    expect(result.config.quality_definition?.qualities?.[0]?.quality).toBe(\"HDTV-720p\");\n    expect(result.config.quality_definition?.qualities?.[1]?.quality).toBe(\"Bluray-1080p\");\n  });\n\n  test(\"should auto-detect QD JSON from TRASH URL and apply preferred_ratio\", async () => {\n    const qdTemplate: TrashQualityDefinition = {\n      trash_id: \"aed34b9f60ee115dfa7918b742336277\",\n      type: \"movie\",\n      qualities: [{ quality: \"Bluray-1080p\", min: 0, preferred: 100, max: 400 }],\n    };\n\n    mockKyGet.mockResolvedValue({\n      text: async () => JSON.stringify(qdTemplate),\n    });\n\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(new Map());\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n    vi.spyOn(trashGuide, \"loadAllQDsFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n\n    const inputConfig: InputConfigArrInstance = {\n      include: [{ template: \"https://example.com/quality-size.json\", source: \"TRASH\", preferred_ratio: 0.5 }],\n      custom_formats: [],\n      quality_profiles: [],\n      api_key: \"test\",\n      base_url: \"http://radarr:7878\",\n    };\n\n    const result = await mergeConfigsAndTemplates({}, inputConfig, \"RADARR\");\n\n    expect(result.config.quality_definition).toBeDefined();\n    expect(result.config.quality_definition?.qualities).toHaveLength(1);\n    // preferred should be interpolated at ratio=0.5: min + (pref - min) * (0.5/0.5) = 0 + (100-0)*1 = 100\n    expect(result.config.quality_definition?.qualities?.[0]?.preferred).toBe(100);\n  });\n\n  test(\"should auto-detect QD from named TRASH include and apply it to quality_definition\", async () => {\n    const trashId = \"aed34b9f60ee115dfa7918b742336277\";\n    const qdTemplate: TrashQualityDefinition = {\n      trash_id: trashId,\n      type: \"movie\",\n      qualities: [\n        { quality: \"HDTV-720p\", min: 10, preferred: 50, max: 67 },\n        { quality: \"Bluray-1080p\", min: 17, preferred: 140, max: 400 },\n      ],\n    };\n\n    const trashQDMap: Map<string, TrashQualityDefinition> = new Map([[trashId, qdTemplate]]);\n\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(new Map());\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n    vi.spyOn(trashGuide, \"loadAllQDsFromTrash\").mockReturnValue(Promise.resolve(trashQDMap));\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n\n    const inputConfig: InputConfigArrInstance = {\n      include: [{ template: trashId, source: \"TRASH\", preferred_ratio: 0.5 }],\n      custom_formats: [],\n      quality_profiles: [],\n      api_key: \"test\",\n      base_url: \"http://radarr:7878\",\n    };\n\n    const result = await mergeConfigsAndTemplates({}, inputConfig, \"RADARR\");\n\n    expect(result.config.quality_definition).toBeDefined();\n    expect(result.config.quality_definition?.qualities).toHaveLength(2);\n    expect(result.config.quality_definition?.qualities?.[0]?.quality).toBe(\"HDTV-720p\");\n    expect(result.config.quality_definition?.qualities?.[1]?.quality).toBe(\"Bluray-1080p\");\n  });\n});\n\nconst dummyProfile = {\n  name: \"profile\",\n  min_format_score: 0,\n  qualities: [],\n  quality_sort: \"sort\",\n  upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n  score_set: \"default\" as keyof import(\"./types/trashguide.types\").TrashScores,\n};\n\ndescribe(\"custom_formats ordering\", () => {\n  beforeEach(() => {\n    vi.resetAllMocks();\n  });\n\n  test(\"should order: includes -> customFormatGroups (from include) -> customFormatGroups (from instance) -> direct custom_formats\", async () => {\n    // Mock template with custom_formats and custom_format_groups\n    const templateCF = { trash_ids: [\"cf-template\"] };\n    const groupCF = { name: \"cf-group\", trash_id: \"cf-group\", required: true };\n    const groupCF2 = { name: \"cf-group2\", trash_id: \"cf-group2\", required: true };\n    const directCF = { trash_ids: [\"cf-direct\"] };\n\n    const templateWithGroup = {\n      custom_formats: [templateCF],\n      custom_format_groups: [{ trash_guide: [{ id: \"group1\" }], assign_scores_to: [{ name: \"profile\" }] }],\n    };\n\n    const recyclarrTemplates: Map<string, MappedTemplates> = new Map([[\"template1\", templateWithGroup]]);\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(\n      Promise.resolve(\n        new Map([\n          [\"group1\", { name: \"group1\", trash_id: \"group1\", custom_formats: [groupCF] }],\n          [\"group2\", { name: \"group2\", trash_id: \"group2\", custom_formats: [groupCF2] }],\n        ]),\n      ),\n    );\n\n    const inputConfig: InputConfigArrInstance = {\n      include: [{ template: \"template1\", source: \"RECYCLARR\" }],\n      custom_format_groups: [{ trash_guide: [{ id: \"group2\" }], assign_scores_to: [{ name: \"profile\" }] }],\n      custom_formats: [directCF],\n      quality_profiles: [dummyProfile],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n\n    const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n    const ids = result.config.custom_formats.map((cf) => cf.trash_ids?.[0]).filter(Boolean);\n    expect(ids).toEqual([\"cf-group\", \"cf-template\", \"cf-group2\", \"cf-direct\"]); // updated order: group first\n  });\n\n  test(\"should handle empty customFormatGroups gracefully\", async () => {\n    const templateCF = { trash_ids: [\"cf-template\"] };\n    const directCF = { trash_ids: [\"cf-direct\"] };\n    const templateWithNoGroup = { custom_formats: [templateCF] };\n    const recyclarrTemplates: Map<string, MappedTemplates> = new Map([[\"template1\", templateWithNoGroup]]);\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n    const inputConfig: InputConfigArrInstance = {\n      include: [{ template: \"template1\", source: \"RECYCLARR\" }],\n      custom_format_groups: [],\n      custom_formats: [directCF],\n      quality_profiles: [dummyProfile],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n    const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n    const ids = result.config.custom_formats.map((cf) => cf.trash_ids?.[0]).filter(Boolean);\n    expect(ids).toEqual([\"cf-template\", \"cf-direct\"]);\n  });\n\n  test(\"should handle only customFormatGroups\", async () => {\n    const groupCF = { name: \"cf-group\", trash_id: \"cf-group\", required: true };\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(new Map());\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(\n      Promise.resolve(new Map([[\"group1\", { name: \"group1\", trash_id: \"group1\", custom_formats: [groupCF] }]])),\n    );\n    const inputConfig: InputConfigArrInstance = {\n      include: [],\n      custom_format_groups: [{ trash_guide: [{ id: \"group1\" }], assign_scores_to: [{ name: \"profile\" }] }],\n      custom_formats: [],\n      quality_profiles: [dummyProfile],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n    const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n    const ids = result.config.custom_formats.map((cf) => cf.trash_ids?.[0]).filter(Boolean);\n    expect(ids).toEqual([\"cf-group\"]);\n  });\n\n  test(\"should overwrite custom format scores in correct order (in template overwrite, group)\", async () => {\n    const testCF1ProfileAssignment: NonNullable<InputConfigCustomFormat[\"assign_scores_to\"]>[number] = {\n      name: \"profile\",\n      score: 1,\n    };\n    const testCF1: InputConfigCustomFormat = { trash_ids: [\"test-cf\"], assign_scores_to: [testCF1ProfileAssignment] };\n\n    const template1: MappedTemplates = {\n      custom_format_groups: [{ trash_guide: [{ id: \"group1\" }], assign_scores_to: [{ name: \"profile\" }] }],\n    };\n    const groupCF1 = { name: \"test-cf\", trash_id: \"test-cf\", required: true };\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(\n      Promise.resolve(new Map([[\"group1\", { name: \"group1\", trash_id: \"group1\", custom_formats: [groupCF1] }]])),\n    );\n\n    const recyclarrTemplates: Map<string, MappedTemplates> = new Map([[\"template1\", template1]]);\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n\n    let inputConfig: InputConfigArrInstance = {\n      include: [{ template: \"template1\", source: \"RECYCLARR\" }],\n      custom_format_groups: [],\n      custom_formats: [],\n      quality_profiles: [dummyProfile],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n\n    let result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n    let cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n    expect(cf?.assign_scores_to?.[0]?.score).toBe(undefined); // should be 0 from template1\n\n    template1.custom_formats = [testCF1];\n    result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n    cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n    expect(cf?.assign_scores_to?.[0]?.score).toBe(1); // should be 0 from template1\n  });\n\n  test(\"should overwrite custom format scores in correct order (in template overwrite)\", async () => {\n    const testCF1ProfileAssignment: NonNullable<InputConfigCustomFormat[\"assign_scores_to\"]>[number] = {\n      name: \"profile\",\n      score: undefined,\n    };\n    const testCF1: InputConfigCustomFormat = { trash_ids: [\"test-cf\"], assign_scores_to: [testCF1ProfileAssignment] };\n\n    const template1: MappedTemplates = {\n      custom_formats: [testCF1],\n      custom_format_groups: [{ trash_guide: [{ id: \"group1\" }], assign_scores_to: [{ name: \"profile\" }] }],\n    };\n    const groupCF1 = { name: \"test-cf\", trash_id: \"test-cf\", required: true };\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(\n      Promise.resolve(new Map([[\"group1\", { name: \"group1\", trash_id: \"group1\", custom_formats: [groupCF1] }]])),\n    );\n\n    const recyclarrTemplates: Map<string, MappedTemplates> = new Map([[\"template1\", template1]]);\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n\n    let inputConfig: InputConfigArrInstance = {\n      include: [{ template: \"template1\", source: \"RECYCLARR\" }],\n      custom_format_groups: [],\n      custom_formats: [],\n      quality_profiles: [dummyProfile],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n\n    let result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n    let cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n    expect(cf?.assign_scores_to?.[0]?.score).toBe(undefined); // should be 0 from template1\n\n    testCF1ProfileAssignment.score = 1; // overwrite score to 1\n    result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n    cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n    expect(cf?.assign_scores_to?.[0]?.score).toBe(1); // should be 0 from template1\n  });\n\n  test(\"should overwrite custom format scores in correct order (in template -> template overwrite) [not supported yet]\", async () => {\n    const testCF1ProfileAssignment: NonNullable<InputConfigCustomFormat[\"assign_scores_to\"]>[number] = {\n      name: \"profile\",\n      score: 2,\n    };\n    const testCF2ProfileAssignment: NonNullable<InputConfigCustomFormat[\"assign_scores_to\"]>[number] = {\n      name: \"profile\",\n      score: 1,\n    };\n    const testCF1: InputConfigCustomFormat = { trash_ids: [\"test-cf\"], assign_scores_to: [testCF1ProfileAssignment] };\n    const testCF2: InputConfigCustomFormat = { trash_ids: [\"test-cf\"], assign_scores_to: [testCF2ProfileAssignment] };\n\n    const template1: MappedTemplates = { include: [{ template: \"template2\", source: \"RECYCLARR\" }] };\n    const template2: MappedTemplates = { custom_formats: [testCF2] };\n    const groupCF1 = { name: \"test-cf\", trash_id: \"test-cf\", required: true };\n\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(\n      Promise.resolve(new Map([[\"group1\", { name: \"group1\", trash_id: \"group1\", custom_formats: [groupCF1] }]])),\n    );\n\n    const recyclarrTemplates: Map<string, MappedTemplates> = new Map([\n      [\"template1\", template1],\n      [\"template2\", template2],\n    ]);\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n\n    let inputConfig: InputConfigArrInstance = {\n      include: [{ template: \"template1\", source: \"RECYCLARR\" }],\n      custom_format_groups: [],\n      custom_formats: [],\n      quality_profiles: [dummyProfile],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n\n    let result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n    let cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n    expect(cf?.assign_scores_to?.[0]?.score).toBe(undefined); // should be 1 if recursive implemented someday\n\n    template1.custom_formats = [testCF1];\n    result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n    cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n    expect(cf?.assign_scores_to?.[0]?.score).toBe(2);\n  });\n\n  test(\"should overwrite custom format scores in correct order (instance over template)\", async () => {\n    const testCF1ProfileAssignment: NonNullable<InputConfigCustomFormat[\"assign_scores_to\"]>[number] = {\n      name: \"profile\",\n      score: 1,\n    };\n    const testCF2ProfileAssignment: NonNullable<InputConfigCustomFormat[\"assign_scores_to\"]>[number] = {\n      name: \"profile\",\n      score: 2,\n    };\n    const testCF1: InputConfigCustomFormat = { trash_ids: [\"test-cf\"], assign_scores_to: [testCF1ProfileAssignment] };\n    const testCF2: InputConfigCustomFormat = { trash_ids: [\"test-cf\"], assign_scores_to: [testCF2ProfileAssignment] };\n\n    const template1: MappedTemplates = {\n      custom_formats: [testCF1],\n      custom_format_groups: [{ trash_guide: [{ id: \"group1\" }], assign_scores_to: [{ name: \"profile\" }] }],\n    };\n\n    const groupCF1 = { name: \"test-cf\", trash_id: \"test-cf\", required: true };\n\n    vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(\n      Promise.resolve(new Map([[\"group1\", { name: \"group1\", trash_id: \"group1\", custom_formats: [groupCF1] }]])),\n    );\n\n    const recyclarrTemplates: Map<string, MappedTemplates> = new Map([[\"template1\", template1]]);\n    vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n    vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n    vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n\n    let inputConfig: InputConfigArrInstance = {\n      include: [{ template: \"template1\", source: \"RECYCLARR\" }],\n      custom_format_groups: [],\n      custom_formats: [],\n      quality_profiles: [dummyProfile],\n      api_key: \"test\",\n      base_url: \"http://sonarr:8989\",\n    };\n\n    let result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n    let cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n    expect(cf?.assign_scores_to?.[0]?.score).toBe(1);\n\n    // Group does not overwrite scores\n    inputConfig.custom_format_groups = [{ trash_guide: [{ id: \"group1\" }], assign_scores_to: [{ name: \"profile\" }] }];\n    result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n    cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n    expect(cf?.assign_scores_to?.[0]?.score).toBe(1);\n\n    inputConfig.custom_formats = [testCF2]; // overwrite with instance config\n    result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n    cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n    expect(cf?.assign_scores_to?.[0]?.score).toBe(2);\n  });\n\n  describe(\"use_default_score flag\", () => {\n    test(\"should reset group score to default with use_default_score: true\", async () => {\n      // Scenario: Group sets score 0, instance CF uses use_default_score: true to reset to default\n      const groupCF1 = { name: \"test-cf\", trash_id: \"test-cf\", required: true };\n      vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(\n        Promise.resolve(new Map([[\"group1\", { name: \"group1\", trash_id: \"group1\", custom_formats: [groupCF1] }]])),\n      );\n\n      const recyclarrTemplates: Map<string, MappedTemplates> = new Map();\n      vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n      vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n      vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n\n      const inputConfig: InputConfigArrInstance = {\n        include: [],\n        custom_format_groups: [{ trash_guide: [{ id: \"group1\" }], assign_scores_to: [{ name: \"profile\", score: 0 }] }],\n        custom_formats: [{ trash_ids: [\"test-cf\"], assign_scores_to: [{ name: \"profile\", use_default_score: true }] }],\n        quality_profiles: [dummyProfile],\n        api_key: \"test\",\n        base_url: \"http://sonarr:8989\",\n      };\n\n      const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n      const cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n      expect(cf?.assign_scores_to?.[0]?.use_default_score).toBe(true);\n      expect(cf?.assign_scores_to?.[0]?.score).toBeUndefined();\n    });\n\n    test(\"should reset template score to default with use_default_score: true\", async () => {\n      // Scenario: Template sets score 10, instance CF uses use_default_score: true to reset to default\n      const templateCF: InputConfigCustomFormat = {\n        trash_ids: [\"test-cf\"],\n        assign_scores_to: [{ name: \"profile\", score: 10 }],\n      };\n      const template1: MappedTemplates = { custom_formats: [templateCF] };\n\n      vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n      const recyclarrTemplates: Map<string, MappedTemplates> = new Map([[\"template1\", template1]]);\n      vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n      vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n      vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n\n      const inputConfig: InputConfigArrInstance = {\n        include: [{ template: \"template1\", source: \"RECYCLARR\" }],\n        custom_format_groups: [],\n        custom_formats: [{ trash_ids: [\"test-cf\"], assign_scores_to: [{ name: \"profile\", use_default_score: true }] }],\n        quality_profiles: [dummyProfile],\n        api_key: \"test\",\n        base_url: \"http://sonarr:8989\",\n      };\n\n      const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n      const cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n      expect(cf?.assign_scores_to?.[0]?.use_default_score).toBe(true);\n    });\n\n    test(\"should prefer use_default_score: true over explicit score in same entry\", async () => {\n      // When both use_default_score: true and score are set, use_default_score takes precedence\n      vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n      vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(new Map());\n      vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n      vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n\n      const inputConfig: InputConfigArrInstance = {\n        include: [],\n        custom_format_groups: [],\n        custom_formats: [\n          {\n            trash_ids: [\"test-cf\"],\n            assign_scores_to: [{ name: \"profile\", score: 100, use_default_score: true }],\n          },\n        ],\n        quality_profiles: [dummyProfile],\n        api_key: \"test\",\n        base_url: \"http://sonarr:8989\",\n      };\n\n      const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n      const cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n      expect(cf?.assign_scores_to?.[0]?.use_default_score).toBe(true);\n      // Score should be undefined when use_default_score is true\n      expect(cf?.assign_scores_to?.[0]?.score).toBeUndefined();\n    });\n\n    test(\"should not override score without use_default_score flag\", async () => {\n      // Scenario: Group sets score 0, instance CF without use_default_score keeps score 0\n      const groupCF1 = { name: \"test-cf\", trash_id: \"test-cf\", required: true };\n      vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(\n        Promise.resolve(new Map([[\"group1\", { name: \"group1\", trash_id: \"group1\", custom_formats: [groupCF1] }]])),\n      );\n\n      vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(new Map());\n      vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n      vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n\n      const inputConfig: InputConfigArrInstance = {\n        include: [],\n        custom_format_groups: [{ trash_guide: [{ id: \"group1\" }], assign_scores_to: [{ name: \"profile\", score: 0 }] }],\n        custom_formats: [{ trash_ids: [\"test-cf\"], assign_scores_to: [{ name: \"profile\" }] }], // no score, no flag\n        quality_profiles: [dummyProfile],\n        api_key: \"test\",\n        base_url: \"http://sonarr:8989\",\n      };\n\n      const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n      const cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n      // Without use_default_score, the group's score 0 should be kept\n      expect(cf?.assign_scores_to?.[0]?.score).toBe(0);\n      expect(cf?.assign_scores_to?.[0]?.use_default_score).toBeFalsy();\n    });\n\n    test(\"should allow explicit score to override use_default_score from earlier\", async () => {\n      // Scenario: First entry has use_default_score: true, later entry has explicit score\n      vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(Promise.resolve(new Map()));\n      vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(new Map());\n      vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n      vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n\n      const inputConfig: InputConfigArrInstance = {\n        include: [],\n        custom_format_groups: [],\n        custom_formats: [\n          { trash_ids: [\"test-cf\"], assign_scores_to: [{ name: \"profile\", use_default_score: true }] },\n          { trash_ids: [\"test-cf\"], assign_scores_to: [{ name: \"profile\", score: 50 }] },\n        ],\n        quality_profiles: [dummyProfile],\n        api_key: \"test\",\n        base_url: \"http://sonarr:8989\",\n      };\n\n      const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n      const cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n      // Later explicit score should override use_default_score\n      expect(cf?.assign_scores_to?.[0]?.score).toBe(50);\n      expect(cf?.assign_scores_to?.[0]?.use_default_score).toBe(false);\n    });\n\n    test(\"complex scenario: template + group + instance with use_default_score\", async () => {\n      // Scenario: Template sets score 10, group sets score 0, instance uses use_default_score\n      const groupCF1 = { name: \"test-cf\", trash_id: \"test-cf\", required: true };\n      vi.spyOn(trashGuide, \"loadTrashCustomFormatGroups\").mockReturnValue(\n        Promise.resolve(new Map([[\"group1\", { name: \"group1\", trash_id: \"group1\", custom_formats: [groupCF1] }]])),\n      );\n\n      const templateCF: InputConfigCustomFormat = {\n        trash_ids: [\"test-cf\"],\n        assign_scores_to: [{ name: \"profile\", score: 10 }],\n      };\n      const template1: MappedTemplates = {\n        custom_formats: [templateCF],\n        custom_format_groups: [{ trash_guide: [{ id: \"group1\" }], assign_scores_to: [{ name: \"profile\" }] }],\n      };\n\n      const recyclarrTemplates: Map<string, MappedTemplates> = new Map([[\"template1\", template1]]);\n      vi.spyOn(reclarrImporter, \"loadRecyclarrTemplates\").mockReturnValue(recyclarrTemplates);\n      vi.spyOn(localImporter, \"loadLocalRecyclarrTemplate\").mockReturnValue(new Map());\n      vi.spyOn(trashGuide, \"loadQPFromTrash\").mockReturnValue(Promise.resolve(new Map()));\n\n      const inputConfig: InputConfigArrInstance = {\n        include: [{ template: \"template1\", source: \"RECYCLARR\" }],\n        custom_format_groups: [],\n        custom_formats: [{ trash_ids: [\"test-cf\"], assign_scores_to: [{ name: \"profile\", use_default_score: true }] }],\n        quality_profiles: [dummyProfile],\n        api_key: \"test\",\n        base_url: \"http://sonarr:8989\",\n      };\n\n      const result = await mergeConfigsAndTemplates({}, inputConfig, \"SONARR\");\n      const cf = result.config.custom_formats.find((cf) => cf.trash_ids?.includes(\"test-cf\"));\n      // use_default_score: true should reset to default, overriding all previous scores\n      expect(cf?.assign_scores_to?.[0]?.use_default_score).toBe(true);\n      expect(cf?.assign_scores_to?.[0]?.score).toBeUndefined();\n    });\n  });\n\n  describe(\"CONFIGARR_ENABLE_MERGE (YAML merge keys)\", () => {\n    const configLocation = \"/config/config.yml\";\n\n    beforeEach(() => {\n      mockExistsSync.mockReturnValue(true);\n    });\n\n    test(\"should merge YAML anchor when enableMerge is true\", () => {\n      const yamlWithMerge = `\nbase: &qb_base\n  type: qbittorrent\n  fields:\n    host: qbittorrent\n    port: 8080\nsonarr:\n  instance1:\n    base_url: http://sonarr:8989\n    api_key: test\n    download_clients:\n      data:\n        - <<: *qb_base\n          name: \"MyQbit\"\n          fields:\n            tv_category: series\n      update_password: false\n`;\n\n      vi.spyOn(env, \"getHelpers\").mockReturnValue({\n        configLocation,\n        secretLocation: \"/config/secrets.yml\",\n        repoPath: \"/repos\",\n        enableMerge: true,\n      });\n      mockReadFileSync.mockImplementation((path: string) => {\n        if (path === configLocation) return yamlWithMerge.trim();\n        return \"\";\n      });\n\n      const raw = readConfigRaw() as Record<string, unknown>;\n      const dc = (raw.sonarr as Record<string, unknown>)?.instance1 as Record<string, unknown>;\n      const data = (dc?.download_clients as Record<string, unknown>)?.data as Record<string, unknown>[];\n      expect(data).toHaveLength(1);\n      // Merge is shallow: base contributed type; override replaced fields entirely\n      expect(data[0]).toMatchObject({\n        name: \"MyQbit\",\n        type: \"qbittorrent\",\n        fields: { tv_category: \"series\" },\n      });\n    });\n\n    test(\"should not merge when enableMerge is false (<< remains literal or alias only)\", () => {\n      const yamlWithMerge = `\nbase: &qb_base\n  type: qbittorrent\nsonarr:\n  instance1:\n    base_url: http://sonarr:8989\n    api_key: test\n    download_clients:\n      data:\n        - <<: *qb_base\n          name: \"MyQbit\"\n`;\n\n      vi.spyOn(env, \"getHelpers\").mockReturnValue({\n        configLocation,\n        secretLocation: \"/config/secrets.yml\",\n        repoPath: \"/repos\",\n        enableMerge: false,\n      });\n      mockReadFileSync.mockImplementation((path: string) => {\n        if (path === configLocation) return yamlWithMerge.trim();\n        return \"\";\n      });\n\n      const raw = readConfigRaw() as Record<string, unknown>;\n      const dc = (raw.sonarr as Record<string, unknown>)?.instance1 as Record<string, unknown>;\n      const data = (dc?.download_clients as Record<string, unknown>)?.data as Record<string, unknown>[];\n      expect(data).toHaveLength(1);\n      const entry = data[0] as Record<string, unknown>;\n      expect(entry.name).toBe(\"MyQbit\");\n      // With merge disabled, type from *qb_base is not merged into this object\n      expect(entry.type).toBeUndefined();\n    });\n  });\n});\n\ndescribe(\"getSecrets\", () => {\n  beforeEach(() => {\n    vi.resetAllMocks();\n    mockFastGlobSync.mockClear();\n    mockExistsSync.mockClear();\n    mockReadFileSync.mockClear();\n    resetSecretsCache();\n  });\n\n  test(\"should load single secret file (backward compatibility)\", () => {\n    const secretLocation = \"/config/secrets.yml\";\n    const secretContent = \"API_KEY: test-key-123\\nOTHER_SECRET: test-value\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    mockFastGlobSync.mockReturnValue([secretLocation]);\n    mockExistsSync.mockReturnValue(true);\n    mockReadFileSync.mockReturnValue(secretContent);\n\n    const secrets = getSecrets();\n\n    expect(secrets).toEqual({\n      API_KEY: \"test-key-123\",\n      OTHER_SECRET: \"test-value\",\n    });\n    expect(mockReadFileSync).toHaveBeenCalledWith(secretLocation, \"utf8\");\n  });\n\n  test(\"should throw error when single secret file does not exist\", () => {\n    const secretLocation = \"/config/secrets-nonexistent.yml\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    // fast-glob returns empty for non-existent file\n    mockFastGlobSync.mockReturnValue([]);\n    // existsSync also returns false\n    mockExistsSync.mockReturnValue(false);\n\n    expect(() => getSecrets()).toThrow(\"Secret file not found.\");\n  });\n\n  test(\"should load multiple secret files via glob pattern\", () => {\n    const secretLocation = \"/config/secrets-glob/*.yml\";\n    const secretFile1 = \"/config/secrets-glob/sonarr.yml\";\n    const secretFile2 = \"/config/secrets-glob/radarr.yml\";\n    const secretContent1 = \"SONARR_API_KEY: sonarr-key-123\";\n    const secretContent2 = \"RADARR_API_KEY: radarr-key-456\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    // Mock fast-glob\n    mockFastGlobSync.mockReturnValue([secretFile1, secretFile2]);\n\n    mockExistsSync.mockImplementation((path) => {\n      return path === secretFile1 || path === secretFile2;\n    });\n\n    mockReadFileSync.mockImplementation((path) => {\n      if (path === secretFile1) return secretContent1;\n      if (path === secretFile2) return secretContent2;\n      return \"\";\n    });\n\n    const secrets = getSecrets();\n\n    expect(secrets).toEqual({\n      SONARR_API_KEY: \"sonarr-key-123\",\n      RADARR_API_KEY: \"radarr-key-456\",\n    });\n    expect(mockFastGlobSync).toHaveBeenCalled();\n  });\n\n  test(\"should merge multiple secret files with later files overriding earlier ones\", () => {\n    const secretLocation = \"/config/secrets-merge/*.yml\";\n    const secretFile1 = \"/config/secrets-merge/common.yml\";\n    const secretFile2 = \"/config/secrets-merge/override.yml\";\n    const secretContent1 = \"API_KEY: original-key\\nCOMMON_VALUE: common\";\n    const secretContent2 = \"API_KEY: overridden-key\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    // Mock fast-glob to return files in alphabetical order\n    mockFastGlobSync.mockReturnValue([secretFile1, secretFile2]);\n\n    mockExistsSync.mockReturnValue(true);\n    mockReadFileSync.mockImplementation((path) => {\n      if (path === secretFile1) return secretContent1;\n      if (path === secretFile2) return secretContent2;\n      return \"\";\n    });\n\n    const secrets = getSecrets();\n\n    // Later file should override earlier file's values\n    expect(secrets).toEqual({\n      API_KEY: \"overridden-key\",\n      COMMON_VALUE: \"common\",\n    });\n  });\n\n  test(\"should handle glob pattern with no matches\", () => {\n    const secretLocation = \"/config/secrets-empty/*.yml\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    // Mock fast-glob to return empty array\n    mockFastGlobSync.mockReturnValue([]);\n\n    const secrets = getSecrets();\n\n    // Should return empty object, not throw\n    expect(secrets).toEqual({});\n  });\n\n  test(\"should skip invalid YAML files and continue with others\", () => {\n    const secretLocation = \"/config/secrets-invalid/*.yml\";\n    const secretFile1 = \"/config/secrets-invalid/valid.yml\";\n    const secretFile2 = \"/config/secrets-invalid/invalid.yml\";\n    const secretContent1 = \"VALID_KEY: valid-value\";\n    const secretContent2 = \"invalid: yaml: content: [unclosed\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    // Mock fast-glob\n    mockFastGlobSync.mockReturnValue([secretFile1, secretFile2]);\n\n    mockExistsSync.mockReturnValue(true);\n    mockReadFileSync.mockImplementation((path) => {\n      if (path === secretFile1) return secretContent1;\n      if (path === secretFile2) return secretContent2;\n      return \"\";\n    });\n\n    // Mock yaml.parse to throw for invalid file\n    const originalParse = yaml.parse;\n    const parseSpy = vi.spyOn(yaml, \"parse\").mockImplementation((content: string) => {\n      if (content === secretContent2) {\n        throw new Error(\"Invalid YAML\");\n      }\n      return originalParse(content);\n    });\n\n    const secrets = getSecrets();\n\n    // Should only contain valid file's content\n    expect(secrets).toEqual({\n      VALID_KEY: \"valid-value\",\n    });\n  });\n\n  test(\"should skip empty files and continue with others\", () => {\n    const secretLocation = \"/config/secrets-empty-files/*.yml\";\n    const secretFile1 = \"/config/secrets-empty-files/empty.yml\";\n    const secretFile2 = \"/config/secrets-empty-files/valid.yml\";\n    const secretContent1 = \"   \\n  \\n  \";\n    const secretContent2 = \"VALID_KEY: valid-value\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    // Mock fast-glob\n    mockFastGlobSync.mockReturnValue([secretFile1, secretFile2]);\n\n    mockExistsSync.mockReturnValue(true);\n    mockReadFileSync.mockImplementation((path) => {\n      if (path === secretFile1) return secretContent1;\n      if (path === secretFile2) return secretContent2;\n      return \"\";\n    });\n\n    const secrets = getSecrets();\n\n    // Should only contain valid file's content\n    expect(secrets).toEqual({\n      VALID_KEY: \"valid-value\",\n    });\n  });\n\n  test(\"should cache secrets after first load\", () => {\n    const secretLocation = \"/config/secrets-cache.yml\";\n    const secretContent = \"API_KEY: test-key\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    mockFastGlobSync.mockReturnValue([secretLocation]);\n    mockExistsSync.mockReturnValue(true);\n    mockReadFileSync.mockReturnValue(secretContent);\n\n    const secrets1 = getSecrets();\n    const secrets2 = getSecrets();\n\n    // Should return same object (cached)\n    expect(secrets1).toBe(secrets2);\n    // Should only read file once (cached on second call)\n    expect(mockReadFileSync).toHaveBeenCalledTimes(1);\n  });\n\n  test(\"should load multiple secret files via comma-separated list\", () => {\n    const secretLocation = \"/config/secrets1.yml,/config/secrets2.yml\";\n    const secretFile1 = \"/config/secrets1.yml\";\n    const secretFile2 = \"/config/secrets2.yml\";\n    const secretContent1 = \"SONARR_API_KEY: sonarr-key-123\";\n    const secretContent2 = \"RADARR_API_KEY: radarr-key-456\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    mockFastGlobSync.mockImplementation((pattern: string) => {\n      if (pattern === \"/config/secrets1.yml\") return [secretFile1];\n      if (pattern === \"/config/secrets2.yml\") return [secretFile2];\n      return [];\n    });\n\n    mockExistsSync.mockImplementation((path) => {\n      return path === secretFile1 || path === secretFile2;\n    });\n\n    mockReadFileSync.mockImplementation((path) => {\n      if (path === secretFile1) return secretContent1;\n      if (path === secretFile2) return secretContent2;\n      return \"\";\n    });\n\n    const secrets = getSecrets();\n\n    expect(secrets).toEqual({\n      SONARR_API_KEY: \"sonarr-key-123\",\n      RADARR_API_KEY: \"radarr-key-456\",\n    });\n  });\n\n  test(\"should merge comma-separated files with later files overriding earlier ones\", () => {\n    const secretLocation = \"/config/common.yml,/config/override.yml\";\n    const secretFile1 = \"/config/common.yml\";\n    const secretFile2 = \"/config/override.yml\";\n    const secretContent1 = \"API_KEY: original-key\\nCOMMON_VALUE: common\";\n    const secretContent2 = \"API_KEY: overridden-key\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    mockFastGlobSync.mockImplementation((pattern: string) => {\n      if (pattern === \"/config/common.yml\") return [secretFile1];\n      if (pattern === \"/config/override.yml\") return [secretFile2];\n      return [];\n    });\n\n    mockExistsSync.mockReturnValue(true);\n    mockReadFileSync.mockImplementation((path) => {\n      if (path === secretFile1) return secretContent1;\n      if (path === secretFile2) return secretContent2;\n      return \"\";\n    });\n\n    const secrets = getSecrets();\n\n    // Later file should override earlier file's values\n    expect(secrets).toEqual({\n      API_KEY: \"overridden-key\",\n      COMMON_VALUE: \"common\",\n    });\n  });\n\n  test(\"should handle comma-separated list with whitespace\", () => {\n    const secretLocation = \"/config/file1.yml , /config/file2.yml , /config/file3.yml\";\n    const secretFile1 = \"/config/file1.yml\";\n    const secretFile2 = \"/config/file2.yml\";\n    const secretFile3 = \"/config/file3.yml\";\n    const secretContent1 = \"KEY1: value1\";\n    const secretContent2 = \"KEY2: value2\";\n    const secretContent3 = \"KEY3: value3\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    mockFastGlobSync.mockImplementation((pattern: string) => {\n      if (pattern === \"/config/file1.yml\") return [secretFile1];\n      if (pattern === \"/config/file2.yml\") return [secretFile2];\n      if (pattern === \"/config/file3.yml\") return [secretFile3];\n      return [];\n    });\n\n    mockExistsSync.mockReturnValue(true);\n    mockReadFileSync.mockImplementation((path) => {\n      if (path === secretFile1) return secretContent1;\n      if (path === secretFile2) return secretContent2;\n      if (path === secretFile3) return secretContent3;\n      return \"\";\n    });\n\n    const secrets = getSecrets();\n\n    expect(secrets).toEqual({\n      KEY1: \"value1\",\n      KEY2: \"value2\",\n      KEY3: \"value3\",\n    });\n  });\n\n  test(\"should handle empty comma-separated list\", () => {\n    const secretLocation = \",,,\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    mockFastGlobSync.mockReturnValue([]);\n\n    const secrets = getSecrets();\n\n    // Should return empty object, not throw\n    expect(secrets).toEqual({});\n  });\n\n  test(\"should skip invalid files in comma-separated list and continue with others\", () => {\n    const secretLocation = \"/config/valid.yml,/config/invalid.yml,/config/another-valid.yml\";\n    const secretFile1 = \"/config/valid.yml\";\n    const secretFile2 = \"/config/invalid.yml\";\n    const secretFile3 = \"/config/another-valid.yml\";\n    const secretContent1 = \"VALID_KEY1: value1\";\n    const secretContent2 = \"invalid: yaml: [unclosed bracket\";\n    const secretContent3 = \"VALID_KEY2: value2\";\n\n    vi.spyOn(env, \"getHelpers\").mockReturnValue({\n      configLocation: \"/config/config.yml\",\n      secretLocation,\n      repoPath: \"/repos\",\n      enableMerge: false,\n    });\n\n    mockFastGlobSync.mockImplementation((pattern: string) => {\n      if (pattern === \"/config/valid.yml\") return [secretFile1];\n      if (pattern === \"/config/invalid.yml\") return [secretFile2];\n      if (pattern === \"/config/another-valid.yml\") return [secretFile3];\n      return [];\n    });\n\n    mockExistsSync.mockReturnValue(true);\n    mockReadFileSync.mockImplementation((path) => {\n      if (path === secretFile1) return secretContent1;\n      if (path === secretFile2) return secretContent2;\n      if (path === secretFile3) return secretContent3;\n      return \"\";\n    });\n\n    const secrets = getSecrets();\n\n    // Should only contain valid files' content (invalid file will fail to parse)\n    expect(secrets).toEqual({\n      VALID_KEY1: \"value1\",\n      VALID_KEY2: \"value2\",\n    });\n  });\n});\n\ndescribe(\"isTrashQualityDefinition\", () => {\n  test(\"returns true for valid QD JSON\", () => {\n    const qd = {\n      trash_id: \"aed34b9f60ee115dfa7918b742336277\",\n      type: \"movie\",\n      qualities: [{ quality: \"Bluray-1080p\", min: 5, preferred: 95, max: 100 }],\n    };\n    expect(isTrashQualityDefinition(qd)).toBe(true);\n  });\n\n  test(\"returns false for quality profile JSON (has items array)\", () => {\n    const qp = {\n      trash_id: \"abc\",\n      name: \"HD Bluray + WEB\",\n      items: [{ name: \"Bluray-1080p\", allowed: true }],\n      formatItems: {},\n      upgradeAllowed: true,\n      cutoff: \"Bluray-1080p\",\n      minFormatScore: 0,\n      cutoffFormatScore: 10000,\n      trash_score_set: \"default\",\n    };\n    expect(isTrashQualityDefinition(qp)).toBe(false);\n  });\n\n  test(\"returns false for null\", () => {\n    expect(isTrashQualityDefinition(null)).toBe(false);\n  });\n\n  test(\"returns false for non-object\", () => {\n    expect(isTrashQualityDefinition(\"string\")).toBe(false);\n  });\n\n  test(\"returns false for empty object\", () => {\n    expect(isTrashQualityDefinition({})).toBe(false);\n  });\n\n  test(\"returns false when qualities array is empty\", () => {\n    expect(isTrashQualityDefinition({ trash_id: \"abc\", qualities: [] })).toBe(false);\n  });\n\n  test(\"returns false when first quality has missing or wrong-type preferred/max\", () => {\n    expect(\n      isTrashQualityDefinition({\n        trash_id: \"abc\",\n        type: \"movie\",\n        qualities: [{ quality: \"Bluray-1080p\", min: 5 }],\n      }),\n    ).toBe(false);\n    expect(\n      isTrashQualityDefinition({\n        trash_id: \"abc\",\n        type: \"movie\",\n        qualities: [{ quality: \"Bluray-1080p\", min: 5, preferred: \"95\", max: 100 }],\n      }),\n    ).toBe(false);\n  });\n\n  test(\"returns false when a non-first quality is malformed\", () => {\n    expect(\n      isTrashQualityDefinition({\n        trash_id: \"abc\",\n        type: \"movie\",\n        qualities: [{ quality: \"SDTV\", min: 2, preferred: 50, max: 100 }, { quality: \"HDTV-720p\" }],\n      }),\n    ).toBe(false);\n  });\n});\n"
  },
  {
    "path": "src/config.ts",
    "content": "import { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport fg from \"fast-glob\";\nimport yaml from \"yaml\";\nimport { NamingConfigResource as RadarrNamingConfigResource } from \"./__generated__/radarr/data-contracts\";\nimport { NamingConfigResource as SonarrNamingConfigResource } from \"./__generated__/sonarr/data-contracts\";\nimport { getHelpers } from \"./env\";\nimport { loadLocalRecyclarrTemplate } from \"./local-importer\";\nimport { logger } from \"./logger\";\nimport { filterInvalidQualityProfiles } from \"./quality-profiles\";\nimport { loadRecyclarrTemplates } from \"./recyclarr-importer\";\nimport {\n  loadAllQDsFromTrash,\n  loadNamingFromTrashRadarr,\n  loadNamingFromTrashSonarr,\n  loadQPFromTrash,\n  loadTrashCustomFormatGroups,\n  transformTrashCFGroups,\n  transformTrashQDs,\n  transformTrashQPCFGroups,\n  transformTrashQPCFs,\n  transformTrashQPToTemplate,\n} from \"./trash-guide\";\nimport { ArrType, MappedMergedTemplates, MappedTemplates } from \"./types/common.types\";\nimport {\n  ConfigArrInstance,\n  ConfigCustomFormat,\n  ConfigIncludeItem,\n  ConfigQualityProfile,\n  ConfigSchema,\n  InputConfigArrInstance,\n  InputConfigCustomFormat,\n  InputConfigCustomFormatGroup,\n  InputConfigIncludeItem,\n  InputConfigInstance,\n  InputConfigMetadataProfile,\n  InputConfigRemotePath,\n  InputConfigSchema,\n  MediaNamingType,\n  MergedConfigInstance,\n} from \"./types/config.types\";\nimport { RemotePathConfigSchema } from \"./remotePaths/remotePath.types\";\nimport { TrashCFGroupMapping, TrashQP, TrashQualityDefinition, TrashQualityDefinitionQuality } from \"./types/trashguide.types\";\nimport { isUrl, loadTemplateFromUrl } from \"./url-template-importer\";\nimport { cloneWithJSON } from \"./util\";\nimport { z } from \"zod\";\n\nlet config: ConfigSchema;\nlet secrets: any;\n\n// For testing: reset the secrets cache\nexport const resetSecretsCache = (): void => {\n  secrets = undefined;\n};\n\nconst secretsTag = {\n  identify: (value: any) => value instanceof String,\n  tag: \"!secret\",\n  resolve(str: string) {\n    return getSecrets()[str];\n  },\n};\n\nconst envTag = {\n  identify: (value: any) => value instanceof String,\n  tag: \"!env\",\n  resolve(str: string) {\n    const envValue = process.env[str];\n\n    if (!envValue) {\n      const message = `Environment variables '${str}' is not set.`;\n      logger.error(message);\n      throw new Error(message);\n    }\n\n    return envValue;\n  },\n};\n\nconst fileTag = {\n  identify: (value: any) => value instanceof String,\n  tag: \"!file\",\n  resolve(path: string) {\n    if (!existsSync(path)) {\n      logger.error(`File \"${path}\" specified in the config does not exist.`);\n      throw new Error(\"File needed by the config not found.\");\n    }\n    return readFileSync(path, \"utf8\");\n  },\n};\n\n// TODO some schema validation. For now only check if something can be imported\nexport const getConfig = (): ConfigSchema => {\n  if (config) {\n    return config;\n  }\n\n  const helpers = getHelpers();\n  const configLocation = helpers.configLocation;\n\n  if (!existsSync(configLocation)) {\n    logger.error(`Config file in location \"${configLocation}\" does not exists.`);\n    throw new Error(\"Config file not found.\");\n  }\n\n  const file = readFileSync(configLocation, \"utf8\");\n\n  const inputConfig = yaml.parse(file, {\n    customTags: [secretsTag, envTag, fileTag],\n    merge: helpers.enableMerge,\n  }) as InputConfigSchema;\n\n  config = transformConfig(inputConfig);\n\n  return config;\n};\n\nexport const readConfigRaw = (): object => {\n  const helpers = getHelpers();\n  const configLocation = helpers.configLocation;\n\n  if (!existsSync(configLocation)) {\n    logger.error(`Config file in location \"${configLocation}\" does not exists.`);\n    throw new Error(\"Config file not found.\");\n  }\n\n  const file = readFileSync(configLocation, \"utf8\");\n\n  logger.debug(`Merging config file with merge: ${helpers.enableMerge}`);\n\n  const inputConfig = yaml.parse(file, {\n    customTags: [secretsTag, envTag, fileTag],\n    merge: helpers.enableMerge,\n  });\n\n  return inputConfig;\n};\n\n/**\n * Expand a path pattern (glob or direct path) into an array of file paths\n */\nconst expandPathPattern = (pattern: string): string[] => {\n  const result = fg.sync(pattern, {\n    absolute: true,\n    onlyFiles: true,\n    caseSensitiveMatch: false,\n  });\n  return Array.isArray(result) ? result.sort() : [];\n};\n\n/**\n * Load and parse a single secret file\n */\nconst loadSecretFile = (filePath: string): any | null => {\n  const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);\n\n  if (!existsSync(resolvedPath)) {\n    logger.warn(`Secret file \"${resolvedPath}\" does not exist. Skipping.`);\n    return null;\n  }\n\n  try {\n    const fileContent = readFileSync(resolvedPath, \"utf8\");\n    if (!fileContent.trim()) {\n      logger.debug(`Secret file \"${resolvedPath}\" is empty. Skipping.`);\n      return null;\n    }\n\n    const parsed = yaml.parse(fileContent);\n    if (parsed && typeof parsed === \"object\") {\n      return parsed;\n    }\n    logger.warn(`Secret file \"${resolvedPath}\" does not contain a valid YAML object. Skipping.`);\n    return null;\n  } catch (error) {\n    logger.error(`Failed to parse secret file \"${resolvedPath}\": ${error instanceof Error ? error.message : String(error)}. Skipping.`);\n    return null;\n  }\n};\n\nexport const getSecrets = () => {\n  if (secrets) {\n    return secrets;\n  }\n\n  const secretLocation = getHelpers().secretLocation;\n\n  // Split by comma and expand each pattern (glob or direct path)\n  const patterns = secretLocation\n    .split(\",\")\n    .map((p) => p.trim())\n    .filter((p) => p.length > 0);\n\n  const allFilePaths: string[] = [];\n  for (const pattern of patterns) {\n    const expanded = expandPathPattern(pattern);\n    allFilePaths.push(...expanded);\n  }\n\n  // Single file path (backward compatibility) - throw error if not found (but not for glob patterns)\n  if (patterns.length === 1 && patterns[0] && allFilePaths.length === 0 && !/[*?\\[\\{]/.test(patterns[0])) {\n    logger.error(`Secret file in location \"${patterns[0]}\" does not exist.`);\n    throw new Error(\"Secret file not found.\");\n  }\n\n  if (allFilePaths.length === 0) {\n    logger.warn(`No secret files found from \"${secretLocation}\". Continuing with empty secrets.`);\n    secrets = {};\n    return secrets;\n  }\n\n  // Load and merge all files\n  const secretObjects = allFilePaths.map(loadSecretFile).filter((obj): obj is any => obj !== null);\n\n  if (secretObjects.length === 0) {\n    logger.warn(`No valid secret files could be loaded from \"${secretLocation}\". Continuing with empty secrets.`);\n    secrets = {};\n    return secrets;\n  }\n\n  logger.debug(`Loaded and merged secrets from ${secretObjects.length} file(s)`);\n  secrets = secretObjects.reduce((merged, current) => ({ ...merged, ...current }), {});\n  return secrets;\n};\n\n// 2024-09-30: Recyclarr assign_scores_to adjustments\nexport const transformConfig = (input: InputConfigSchema): ConfigSchema => {\n  const mappedCustomFormats = (arrInput: Record<string, InputConfigArrInstance> = {}): Record<string, ConfigArrInstance> => {\n    return Object.entries(arrInput).reduce(\n      (p, [key, value]) => {\n        const mappedCustomFormats = (value.custom_formats || []).map<ConfigCustomFormat>((cf) => {\n          const { assign_scores_to, quality_profiles, ...rest } = cf;\n\n          if (quality_profiles) {\n            logger.warn(\n              `Deprecated: (Instance '${key}') For custom_formats please rename 'quality_profiles' to 'assign_scores_to'. See recyclarr v7.2.0`,\n            );\n          }\n\n          const mapped_assign_scores = quality_profiles ?? assign_scores_to;\n\n          if (!mapped_assign_scores) {\n            logger.debug(`No assign_scores_to or quality_profiles defined for CF entry '${cf.trash_ids}' in instance '${key}'`);\n          }\n\n          return { ...rest, assign_scores_to: mapped_assign_scores };\n        });\n\n        p[key] = { ...value, include: value.include?.map(parseIncludes), custom_formats: mappedCustomFormats };\n        return p;\n      },\n      {} as Record<string, ConfigArrInstance>,\n    );\n  };\n\n  return {\n    ...input,\n    radarr: mappedCustomFormats(input.radarr),\n    sonarr: mappedCustomFormats(input.sonarr),\n    whisparr: mappedCustomFormats(input.whisparr),\n    readarr: mappedCustomFormats(input.readarr),\n    lidarr: mappedCustomFormats(input.lidarr),\n  };\n};\n\nexport const parseIncludes = (input: InputConfigIncludeItem): ConfigIncludeItem => ({\n  template: input.template,\n  source: input.source ?? \"RECYCLARR\",\n});\n\n/**\n * Validate remote path mappings configuration\n * Checks for duplicate host + remote_path combinations and validates structure\n * @throws Error if validation fails\n */\nconst validateRemotePaths = (remotePaths: InputConfigRemotePath[]): void => {\n  const keys = new Set<string>();\n  for (const config of remotePaths) {\n    // Validate structure using Zod schema\n    try {\n      RemotePathConfigSchema.parse(config);\n    } catch (error) {\n      throw new Error(`Invalid remote path config: ${error instanceof Error ? error.message : String(error)}`);\n    }\n\n    // Check for duplicates by host + remote_path combination\n    // Normalize path for duplicate detection (remove trailing slashes)\n    const normalizedRemotePath = config.remote_path.replace(/\\/+$/, \"\");\n    const key = `${config.host}||${normalizedRemotePath}`;\n    if (keys.has(key)) {\n      throw new Error(`Duplicate remote path mapping: '${config.host} + ${config.remote_path}' already configured.`);\n    }\n    keys.add(key);\n  }\n};\n\nexport const validateConfig = (input: InputConfigInstance): MergedConfigInstance => {\n  // TODO add validation and warnings like assign_scores. Setting default values not always the best\n\n  const preferredRatio = input.quality_definition?.preferred_ratio;\n\n  if (preferredRatio != null && (preferredRatio < 0 || preferredRatio > 1)) {\n    logger.warn(`QualityDefinition: PreferredRatio must be between 0 and 1. Ignoring`);\n    delete input.quality_definition![\"preferred_ratio\"];\n  }\n\n  // Validate remote path mappings if present\n  if (input.download_clients?.remote_paths) {\n    validateRemotePaths(input.download_clients.remote_paths);\n  }\n\n  return {\n    ...input,\n    custom_formats: (input.custom_formats || []).map((e) => ({\n      trash_ids: e.trash_ids,\n      assign_scores_to: e.assign_scores_to ?? e.quality_profiles ?? [],\n    })),\n  };\n};\n\nconst expandAndAppendCustomFormatGroups = (\n  customFormatGroups: InputConfigCustomFormatGroup[] | undefined,\n  trashCFGroupMapping: TrashCFGroupMapping,\n  mergedTemplates: MappedMergedTemplates,\n) => {\n  if (!customFormatGroups || customFormatGroups.length === 0) return;\n  const expanded = transformTrashCFGroups(trashCFGroupMapping, customFormatGroups);\n  if (expanded.length > 0) {\n    logger.debug(`Expanded and appended ${expanded.length} custom formats from customFormatGroups`);\n    mergedTemplates.custom_formats.push(...expanded);\n  }\n};\n\nconst includeRecyclarrTemplate = (\n  template: MappedTemplates,\n  {\n    mergedTemplates,\n    trashCFGroupMapping,\n  }: {\n    mergedTemplates: MappedMergedTemplates;\n    trashCFGroupMapping: TrashCFGroupMapping;\n  },\n) => {\n  // First expand and append group custom formats\n  if (template.custom_format_groups) {\n    expandAndAppendCustomFormatGroups(template.custom_format_groups, trashCFGroupMapping, mergedTemplates);\n  }\n  // Then push direct custom formats after, so they always win\n  if (template.custom_formats) {\n    mergedTemplates.custom_formats.push(...template.custom_formats);\n  }\n\n  if (template.quality_definition) {\n    mergedTemplates.quality_definition = {\n      ...mergedTemplates.quality_definition,\n      ...template.quality_definition,\n      qualities: [...(mergedTemplates.quality_definition?.qualities || []), ...(template.quality_definition.qualities || [])],\n    };\n  }\n\n  if (template.quality_profiles) {\n    for (const qp of template.quality_profiles) {\n      mergedTemplates.quality_profiles.push(qp);\n    }\n  }\n\n  if (template.metadata_profiles) {\n    mergedTemplates.metadata_profiles = [...(mergedTemplates.metadata_profiles || []), ...template.metadata_profiles];\n  }\n\n  if (template.delete_unmanaged_metadata_profiles) {\n    mergedTemplates.delete_unmanaged_metadata_profiles = template.delete_unmanaged_metadata_profiles;\n  }\n\n  if (template.media_management) {\n    mergedTemplates.media_management = { ...mergedTemplates.media_management, ...template.media_management };\n  }\n\n  if (template.media_naming) {\n    mergedTemplates.media_naming = { ...mergedTemplates.media_naming, ...template.media_naming };\n  }\n\n  if (template.media_naming_api) {\n    mergedTemplates.media_naming_api = { ...mergedTemplates.media_naming_api, ...template.media_naming_api };\n  }\n\n  if (template.customFormatDefinitions) {\n    if (Array.isArray(template.customFormatDefinitions)) {\n      mergedTemplates.customFormatDefinitions = [...(mergedTemplates.customFormatDefinitions || []), ...template.customFormatDefinitions];\n    } else {\n      logger.warn(`CustomFormatDefinitions in template must be an array. Ignoring.`);\n    }\n  }\n\n  if (template.root_folders) {\n    if (Array.isArray(template.root_folders)) {\n      mergedTemplates.root_folders = [...(mergedTemplates.root_folders || []), ...template.root_folders];\n    } else {\n      logger.warn(`Root folders in template must be an array. Ignoring.`);\n    }\n  }\n\n  if (template.delay_profiles) {\n    mergedTemplates.delay_profiles = template.delay_profiles;\n  }\n\n  // TODO Ignore recursive include for now\n  if (template.include) {\n    logger.warn(`Recursive includes not supported at the moment. Ignoring.`);\n  }\n};\n\n// TODO: local TRaSH-Guides QP templates do not work yet\nconst includeTrashTemplate = (\n  template: TrashQP,\n  {\n    mergedTemplates,\n    trashCFGroupMapping,\n    customFormatGroups,\n    useExcludeSemantics,\n  }: {\n    mergedTemplates: MappedMergedTemplates;\n    trashCFGroupMapping: TrashCFGroupMapping;\n    customFormatGroups: InputConfigCustomFormatGroup[];\n    useExcludeSemantics: boolean;\n  },\n) => {\n  // useExcludeSemantics=true means use old TRaSH-Guides behavior (before Feb 2026)\n  // This affects both CF groups semantics and quality ordering\n  mergedTemplates.quality_profiles.push(transformTrashQPToTemplate(template, useExcludeSemantics));\n  mergedTemplates.custom_formats.push(transformTrashQPCFs(template));\n\n  // For TrashGuide profiles, check and include default CF groups\n  const requiredCFsFromCFGroups = transformTrashQPCFGroups(template, trashCFGroupMapping, useExcludeSemantics);\n\n  const numberOfCfsLoaded = requiredCFsFromCFGroups.reduce((p, c) => {\n    (c.trash_ids || []).forEach((id) => p.add(id));\n    return p;\n  }, new Set<string>());\n\n  // Log how many CFs were loaded from groups\n  logger.info(`Loaded ${numberOfCfsLoaded.size} default CFs from CF-Groups for TRaSH-Guide profile '${template.name}'`);\n  mergedTemplates.custom_formats.push(...requiredCFsFromCFGroups);\n};\n\nconst TrashQualityDefinitionQualitySchema = z.object({\n  quality: z.string(),\n  title: z.string().optional(),\n  min: z.number(),\n  preferred: z.number(),\n  max: z.number(),\n});\n\nconst TrashQualityDefinitionSchema = z.object({\n  trash_id: z.string(),\n  type: z.string(),\n  qualities: z.array(TrashQualityDefinitionQualitySchema).min(1),\n});\n\nexport const isTrashQualityDefinition = (json: unknown): json is TrashQualityDefinition => {\n  const result = TrashQualityDefinitionSchema.safeParse(json);\n  if (!result.success) return false;\n  if (Array.isArray((json as Record<string, unknown>).items)) return false;\n  return true;\n};\n\nconst applyQualityDefinitionFromInclude = (\n  qd: TrashQualityDefinition,\n  preferredRatio: number | undefined,\n  mergedTemplates: MappedMergedTemplates,\n): void => {\n  const qualities: TrashQualityDefinitionQuality[] =\n    preferredRatio != null && preferredRatio >= 0 && preferredRatio <= 1 ? transformTrashQDs(qd, preferredRatio) : qd.qualities;\n\n  mergedTemplates.quality_definition = {\n    ...mergedTemplates.quality_definition,\n    qualities: [...(mergedTemplates.quality_definition?.qualities || []), ...qualities],\n  };\n  logger.info(`QualityDefinition: Applied '${qd.type}' from include (${qualities.length} qualities).`);\n};\n\nconst includeTemplateOrderDefault = async (\n  include: InputConfigIncludeItem[],\n  {\n    recyclarr,\n    local,\n    trash,\n    trashQD,\n    trashCFGroupMapping,\n    useExcludeSemantics,\n  }: {\n    recyclarr: Map<string, MappedTemplates>;\n    local: Map<string, MappedTemplates>;\n    trash: Map<string, TrashQP>;\n    trashQD: Map<string, TrashQualityDefinition>;\n    trashCFGroupMapping: TrashCFGroupMapping;\n    useExcludeSemantics: boolean;\n  },\n  { mergedTemplates }: { mergedTemplates: MappedMergedTemplates },\n) => {\n  const mappedIncludes = include.reduce<{\n    local: InputConfigIncludeItem[];\n    recyclarr: InputConfigIncludeItem[];\n    trash: InputConfigIncludeItem[];\n    url: InputConfigIncludeItem[];\n  }>(\n    (previous, current) => {\n      // Check if template is a URL - all URLs go to url array, source is passed to loader\n      if (isUrl(current.template)) {\n        previous.url.push(current);\n        return previous;\n      }\n\n      switch (current.source) {\n        case \"TRASH\":\n          if (trash.has(current.template) || trashQD.has(current.template)) {\n            previous.trash.push(current);\n          } else {\n            logger.warn(`Included 'TRASH' template: ${current.template} not found.`);\n          }\n          break;\n        case \"RECYCLARR\":\n        // HINT: hard separation would break current default functionality.\n        // if (recyclarr.has(current.template)) {\n        //   previous.recyclarr.push(current);\n        // } else {\n        //   logger.warn(`Included 'RECYCLARR' template: ${current.template} not found.`);\n        // }\n        // break;\n        case undefined:\n          let recyclarrFound = false;\n          let localFound = false;\n\n          if (recyclarr.has(current.template)) {\n            recyclarrFound = true;\n          }\n\n          if (local.has(current.template)) {\n            localFound = true;\n          }\n\n          if (recyclarrFound === true && localFound === true) {\n            logger.warn(`Found matching 'RECYCLARR' and 'LOCAL' template for '${current.template}. Using 'LOCAL'.`);\n            previous.local.push(current);\n          } else if (recyclarrFound === true) {\n            previous.recyclarr.push(current);\n          } else if (localFound === true) {\n            previous.local.push(current);\n          } else {\n            logger.warn(`No matching 'RECYCLARR' or 'LOCAL' template for '${current.template}'`);\n          }\n\n          break;\n        default:\n          logger.warn(`Unknown source type for template requested: '${current.source}'. Ignoring.`);\n      }\n\n      return previous;\n    },\n    { recyclarr: [], trash: [], local: [], url: [] },\n  );\n\n  logger.info(\n    `Found ${include.length} templates to include. Mapped to [recyclarr]=${mappedIncludes.recyclarr.length}, [local]=${mappedIncludes.local.length}, [trash]=${mappedIncludes.trash.length}, [url]=${mappedIncludes.url.length} ...`,\n  );\n\n  // Process URL templates\n  for (const e of mappedIncludes.url) {\n    const resolvedTemplate = await loadTemplateFromUrl(e.template, e.source);\n    if (resolvedTemplate == null) {\n      logger.warn(`Failed to load template from URL: '${e.template}'`);\n      continue;\n    }\n\n    // Route to appropriate handler based on source\n    if (e.source === \"TRASH\") {\n      if (isTrashQualityDefinition(resolvedTemplate)) {\n        applyQualityDefinitionFromInclude(resolvedTemplate, e.preferred_ratio, mergedTemplates);\n      } else {\n        includeTrashTemplate(resolvedTemplate as TrashQP, {\n          mergedTemplates,\n          trashCFGroupMapping,\n          customFormatGroups: [],\n          useExcludeSemantics,\n        });\n      }\n    } else {\n      includeRecyclarrTemplate(resolvedTemplate as MappedTemplates, { mergedTemplates, trashCFGroupMapping });\n    }\n  }\n\n  mappedIncludes.trash.forEach((e) => {\n    // Check QD map first — quality definitions have a different structure than quality profiles\n    const qdTemplate = trashQD.get(e.template);\n    if (qdTemplate) {\n      applyQualityDefinitionFromInclude(qdTemplate, e.preferred_ratio, mergedTemplates);\n      return;\n    }\n\n    const resolvedTemplate = trash.get(e.template);\n    if (resolvedTemplate == null) {\n      logger.warn(`Unknown 'trash' template requested: '${e.template}'`);\n      return;\n    }\n    includeTrashTemplate(resolvedTemplate, {\n      mergedTemplates,\n      trashCFGroupMapping,\n      customFormatGroups: [],\n      useExcludeSemantics,\n    });\n  });\n  mappedIncludes.recyclarr.forEach((e) => {\n    const resolvedTemplate = recyclarr.get(e.template);\n    if (resolvedTemplate == null) {\n      logger.warn(`Unknown 'recyclarr' template requested: '${e.template}'`);\n      return;\n    }\n    includeRecyclarrTemplate(resolvedTemplate, { mergedTemplates, trashCFGroupMapping });\n  });\n  mappedIncludes.local.forEach((e) => {\n    const resolvedTemplate = local.get(e.template);\n    if (resolvedTemplate == null) {\n      logger.warn(`Unknown 'local' template requested: '${e.template}'`);\n      return;\n    }\n    includeRecyclarrTemplate(resolvedTemplate, { mergedTemplates, trashCFGroupMapping });\n  });\n};\n\ntype MergedScoreInfo = {\n  score?: number;\n  use_default_score?: boolean;\n};\n\nconst mergeAndReduceCustomFormats = (cfs: InputConfigCustomFormat[]) => {\n  const idToQualityProfileToScore = new Map<string, Map<string, MergedScoreInfo>>();\n\n  cfs.forEach((cf) => {\n    if (!cf.trash_ids || cf.trash_ids.length === 0) {\n      logger.info(\n        `Custom format entry does not have trash_ids defined (empty trash_ids or not defined at all. Cleanup your config to remove this log). Entry: ${JSON.stringify(cf)}`,\n      );\n      return;\n    }\n\n    cf.trash_ids.forEach((id) => {\n      if (!idToQualityProfileToScore.has(id)) {\n        idToQualityProfileToScore.set(id, new Map());\n      }\n\n      const existing = idToQualityProfileToScore.get(id)!;\n\n      [...(cf.quality_profiles || []), ...(cf.assign_scores_to || [])].forEach((qp) => {\n        const hasUseDefaultScore = qp.use_default_score === true;\n\n        if (!existing.has(qp.name)) {\n          // If use_default_score is true, set score to undefined (will use default during resolution)\n          existing.set(qp.name, {\n            score: hasUseDefaultScore ? undefined : qp.score,\n            use_default_score: hasUseDefaultScore,\n          });\n        } else {\n          const current = existing.get(qp.name)!;\n\n          // If use_default_score is explicitly set to true, always override\n          if (hasUseDefaultScore) {\n            existing.set(qp.name, { score: undefined, use_default_score: true });\n          } else if (qp.score != null && qp.score != current.score) {\n            // If explicit score is set and different from current, update it\n            existing.set(qp.name, { score: qp.score, use_default_score: false });\n          }\n          // Otherwise, keep existing value (passive - no override)\n        }\n      });\n    });\n  });\n\n  return Array.from(idToQualityProfileToScore.entries()).map(([trashId, qualityProfiles]) => {\n    const assign_scores_to = Array.from(qualityProfiles.entries()).map(([name, info]) => ({\n      name,\n      score: info.score,\n      use_default_score: info.use_default_score,\n    }));\n\n    const result: InputConfigCustomFormat = {\n      trash_ids: [trashId],\n      assign_scores_to,\n    };\n\n    return result;\n  });\n};\n\n/**\n * Load data from trash, recyclarr, custom configs and merge.\n * Afterwards do sanitize and check against required configuration.\n * @param instanceConfig\n * @param arrType\n */\nexport const mergeConfigsAndTemplates = async (\n  globalConfig: InputConfigSchema,\n  instanceConfig: InputConfigArrInstance,\n  arrType: ArrType,\n): Promise<{ config: MergedConfigInstance }> => {\n  const localTemplateMap = loadLocalRecyclarrTemplate(arrType);\n  let recyclarrTemplateMap: Map<string, MappedTemplates> = new Map();\n  let trashTemplates: Map<string, TrashQP> = new Map();\n  let trashQDTemplates: Map<string, TrashQualityDefinition> = new Map();\n  let trashCFGroupMapping: TrashCFGroupMapping = new Map();\n  if (arrType === \"RADARR\" || arrType === \"SONARR\") {\n    // TODO: separation maybe not the best. Maybe time to split up processing for each arrType\n    recyclarrTemplateMap = loadRecyclarrTemplates(arrType);\n    trashTemplates = await loadQPFromTrash(arrType);\n    trashQDTemplates = await loadAllQDsFromTrash(arrType);\n    trashCFGroupMapping = await loadTrashCustomFormatGroups(arrType);\n  }\n  logger.debug(\n    `Loaded ${recyclarrTemplateMap.size} Recyclarr templates, ${localTemplateMap.size} local templates and ${trashTemplates.size} trash templates.`,\n  );\n  const mergedTemplates: MappedMergedTemplates = {\n    custom_formats: [],\n    quality_profiles: [],\n  };\n  // Determine which CF group semantics to use based on compatibility flag\n  // When true: use old \"exclude\" semantics (apply to all except excluded)\n  // When false/undefined: use new \"include\" semantics (apply only to included)\n  const useExcludeSemantics = globalConfig.compatibilityTrashGuide20260219Enabled === true;\n  if (instanceConfig.include) {\n    await includeTemplateOrderDefault(\n      instanceConfig.include,\n      {\n        recyclarr: recyclarrTemplateMap,\n        local: localTemplateMap,\n        trash: trashTemplates,\n        trashQD: trashQDTemplates,\n        trashCFGroupMapping,\n        useExcludeSemantics,\n      },\n      {\n        mergedTemplates,\n      },\n    );\n  }\n\n  // Now handle instanceConfig custom_format_groups before direct custom_formats\n  if (instanceConfig.custom_format_groups) {\n    expandAndAppendCustomFormatGroups(instanceConfig.custom_format_groups, trashCFGroupMapping, mergedTemplates);\n  }\n  if (instanceConfig.custom_formats) {\n    mergedTemplates.custom_formats.push(...instanceConfig.custom_formats);\n  }\n\n  if (instanceConfig.delete_unmanaged_custom_formats) {\n    mergedTemplates.delete_unmanaged_custom_formats = instanceConfig.delete_unmanaged_custom_formats;\n  }\n\n  if (instanceConfig.delete_unmanaged_quality_profiles) {\n    mergedTemplates.delete_unmanaged_quality_profiles = instanceConfig.delete_unmanaged_quality_profiles;\n  }\n\n  if (instanceConfig.quality_profiles) {\n    mergedTemplates.quality_profiles.push(...instanceConfig.quality_profiles);\n  }\n\n  if (instanceConfig.media_management && Object.keys(instanceConfig.media_management).length > 0) {\n    mergedTemplates.media_management = { ...mergedTemplates.media_management, ...instanceConfig.media_management };\n  }\n\n  if (instanceConfig.media_naming && Object.keys(instanceConfig.media_naming).length > 0) {\n    mergedTemplates.media_naming_api = {\n      ...mergedTemplates.media_naming_api,\n      ...(await mapConfigMediaNamingToApi(arrType, instanceConfig.media_naming)),\n    };\n  }\n\n  if (instanceConfig.media_naming_api && Object.keys(instanceConfig.media_naming_api).length > 0) {\n    mergedTemplates.media_naming_api = { ...mergedTemplates.media_naming_api, ...instanceConfig.media_naming_api };\n  }\n\n  if (instanceConfig.quality_definition) {\n    mergedTemplates.quality_definition = {\n      ...mergedTemplates.quality_definition,\n      ...instanceConfig.quality_definition,\n      qualities: [...(mergedTemplates.quality_definition?.qualities || []), ...(instanceConfig.quality_definition.qualities || [])],\n    };\n  }\n\n  if (globalConfig.customFormatDefinitions) {\n    if (Array.isArray(globalConfig.customFormatDefinitions)) {\n      mergedTemplates.customFormatDefinitions = [\n        ...(mergedTemplates.customFormatDefinitions || []),\n        ...globalConfig.customFormatDefinitions,\n      ];\n    } else {\n      logger.warn(`CustomFormatDefinitions in global config file must be an array. Ignoring.`);\n    }\n  }\n\n  if (instanceConfig.customFormatDefinitions) {\n    if (Array.isArray(instanceConfig.customFormatDefinitions)) {\n      mergedTemplates.customFormatDefinitions = [\n        ...(mergedTemplates.customFormatDefinitions || []),\n        ...instanceConfig.customFormatDefinitions,\n      ];\n    } else {\n      logger.warn(`CustomFormatDefinitions in instance config file must be an array. Ignoring.`);\n    }\n  }\n\n  // Rename quality profiles\n  if (instanceConfig.renameQualityProfiles && instanceConfig.renameQualityProfiles.length > 0) {\n    const renameOrder: string[] = [];\n\n    instanceConfig.renameQualityProfiles.forEach((e) => {\n      const renameFrom = e.from;\n      const renameTo = e.to;\n\n      renameOrder.push(`'${renameFrom}' -> '${renameTo}'`);\n\n      let renamedQPReferences = 0;\n      let renamedCFAssignments = 0;\n\n      mergedTemplates.quality_profiles.forEach((p) => {\n        if (p.name === renameFrom) {\n          p.name = renameTo;\n          renamedQPReferences += 1;\n        }\n      });\n\n      mergedTemplates.custom_formats.forEach((p) => {\n        p.assign_scores_to?.some((cf) => {\n          if (cf.name === renameFrom) {\n            cf.name = renameTo;\n            renamedCFAssignments += 1;\n            return true;\n          }\n          return false;\n        });\n      });\n\n      if (renamedQPReferences + renamedCFAssignments > 0) {\n        logger.debug(\n          `Renamed profile from '${renameFrom}' to '${renameTo}'. Found QP references ${renamedQPReferences}, CF references ${renamedCFAssignments}`,\n        );\n      }\n    });\n\n    if (renameOrder.length > 0) {\n      logger.debug(`Will rename quality profiles in this order: ${renameOrder}`);\n    }\n  }\n\n  // Cloning quality profiles\n  if (instanceConfig.cloneQualityProfiles && instanceConfig.cloneQualityProfiles.length > 0) {\n    const cloneOrder: string[] = [];\n\n    const clonedReorderedProfiles: ConfigQualityProfile[] = [];\n\n    instanceConfig.cloneQualityProfiles.forEach((e) => {\n      const cloneSource = e.from;\n      const cloneTarget = e.to;\n\n      cloneOrder.push(`'${cloneSource}' -> '${cloneTarget}'`);\n\n      let cloneQPReferences = 0;\n      let cloneCFAssignments = 0;\n\n      mergedTemplates.quality_profiles.forEach((p) => {\n        clonedReorderedProfiles.push(p);\n\n        if (p.name === cloneSource) {\n          const clonedQP = cloneWithJSON(p);\n          clonedQP.name = cloneTarget;\n          clonedReorderedProfiles.push(clonedQP);\n          cloneQPReferences++;\n        }\n      });\n\n      mergedTemplates.custom_formats.forEach((p) => {\n        const cf = p.assign_scores_to?.find((cf) => {\n          if (cf.name === cloneSource) {\n            cloneCFAssignments++;\n            return true;\n          }\n          return false;\n        });\n\n        if (cf) {\n          p.assign_scores_to?.push({ name: cloneTarget, score: cf.score });\n        }\n      });\n\n      if (cloneQPReferences + cloneCFAssignments > 0) {\n        logger.debug(\n          `Cloning profile: source '${cloneSource}' - target '${cloneTarget}'. Found QP references ${cloneQPReferences}, CF references ${cloneCFAssignments}`,\n        );\n      }\n    });\n\n    mergedTemplates.quality_profiles = clonedReorderedProfiles;\n\n    if (cloneOrder.length > 0) {\n      logger.debug(`Will clone quality profiles in this order: ${cloneOrder}`);\n    }\n  }\n\n  const recyclarrProfilesMerged = mergedTemplates.quality_profiles.reduce<Map<string, ConfigQualityProfile>>((p, c) => {\n    const profile = p.get(c.name);\n\n    if (profile == null) {\n      p.set(c.name, c);\n    } else {\n      p.set(c.name, {\n        ...profile,\n        ...c,\n        reset_unmatched_scores: {\n          enabled: c.reset_unmatched_scores?.enabled ?? profile.reset_unmatched_scores?.enabled ?? true,\n          except: c.reset_unmatched_scores?.except ?? profile.reset_unmatched_scores?.except,\n        },\n        upgrade: {\n          ...profile.upgrade,\n          ...c.upgrade,\n        },\n      });\n    }\n\n    return p;\n  }, new Map());\n\n  mergedTemplates.quality_profiles = Array.from(recyclarrProfilesMerged.values());\n\n  mergedTemplates.quality_profiles = filterInvalidQualityProfiles(mergedTemplates.quality_profiles);\n\n  // merge profiles from recyclarr templates into one\n  const qualityProfilesMerged = mergedTemplates.quality_profiles.reduce((p, c) => {\n    let existingQp = p.get(c.name);\n\n    if (!existingQp) {\n      p.set(c.name, { ...c });\n    } else {\n      existingQp = {\n        ...existingQp,\n        ...c,\n        // Overwriting qualities array for now\n        upgrade: { ...existingQp.upgrade, ...c.upgrade },\n        reset_unmatched_scores: {\n          ...existingQp.reset_unmatched_scores,\n          ...c.reset_unmatched_scores,\n          enabled: (c.reset_unmatched_scores?.enabled ?? existingQp.reset_unmatched_scores?.enabled) || false,\n        },\n      };\n      p.set(c.name, existingQp);\n    }\n\n    return p;\n  }, new Map<string, ConfigQualityProfile>());\n\n  mergedTemplates.quality_profiles = Array.from(qualityProfilesMerged.values());\n\n  if (instanceConfig.metadata_profiles) {\n    mergedTemplates.metadata_profiles = [...(mergedTemplates.metadata_profiles || []), ...instanceConfig.metadata_profiles];\n\n    // Merge by name: if a profile with the same name exists in both template and instance,\n    // instance config takes precedence (overwrites)\n    const metadataProfilesByName = new Map<string, InputConfigMetadataProfile>();\n\n    for (const profile of mergedTemplates.metadata_profiles) {\n      metadataProfilesByName.set(profile.name, profile);\n    }\n\n    mergedTemplates.metadata_profiles = Array.from(metadataProfilesByName.values());\n  }\n\n  if (instanceConfig.delete_unmanaged_metadata_profiles) {\n    const templateIgnore = mergedTemplates.delete_unmanaged_metadata_profiles?.ignore ?? [];\n    const instanceIgnore = instanceConfig.delete_unmanaged_metadata_profiles.ignore ?? [];\n    const mergedIgnore = [...new Set([...templateIgnore, ...instanceIgnore])];\n\n    mergedTemplates.delete_unmanaged_metadata_profiles = {\n      enabled: instanceConfig.delete_unmanaged_metadata_profiles.enabled,\n      ignore: mergedIgnore,\n    };\n  }\n\n  if (instanceConfig.root_folders) {\n    mergedTemplates.root_folders = [...(mergedTemplates.root_folders || []), ...instanceConfig.root_folders];\n  }\n\n  if (mergedTemplates.root_folders) {\n    // cleanup duplicates by path\n    const seenPaths = new Set<string>();\n    mergedTemplates.root_folders = mergedTemplates.root_folders.filter((folder) => {\n      const path = typeof folder === \"string\" ? folder : folder.path;\n      if (seenPaths.has(path)) {\n        return false;\n      }\n      seenPaths.add(path);\n      return true;\n    });\n  }\n\n  // Overwrite delay_profiles if defined in instanceConfig\n  if (instanceConfig.delay_profiles) {\n    mergedTemplates.delay_profiles = instanceConfig.delay_profiles;\n  }\n\n  // Merge download_clients if defined in instanceConfig\n  if (instanceConfig.download_clients) {\n    const existingData = mergedTemplates.download_clients?.data || [];\n    const instanceData = instanceConfig.download_clients.data || [];\n    const existingDeleteManaged = mergedTemplates.download_clients?.delete_unmanaged;\n    const instanceDeleteManaged = instanceConfig.download_clients.delete_unmanaged;\n    const existingConfig = mergedTemplates.download_clients?.config;\n    const instanceConfig_ = instanceConfig.download_clients.config;\n    const existingRemotePaths = mergedTemplates.download_clients?.remote_paths || [];\n    const instanceRemotePaths = instanceConfig.download_clients.remote_paths || [];\n\n    mergedTemplates.download_clients = {\n      data: [...existingData, ...instanceData],\n      update_password: instanceConfig.download_clients.update_password,\n      delete_unmanaged: instanceDeleteManaged ? instanceDeleteManaged : existingDeleteManaged,\n      config: instanceConfig_ ? { ...existingConfig, ...instanceConfig_ } : existingConfig,\n      remote_paths: [...existingRemotePaths, ...instanceRemotePaths],\n      delete_unmanaged_remote_paths: instanceConfig.download_clients.delete_unmanaged_remote_paths,\n    };\n  }\n\n  if (mergedTemplates.custom_formats && mergedTemplates.custom_formats.length > 0) {\n    // Merge custom formats with same trash_ids\n    mergedTemplates.custom_formats = mergeAndReduceCustomFormats(mergedTemplates.custom_formats);\n  }\n\n  const validatedConfig = validateConfig(mergedTemplates);\n  logger.debug(`Merged config: '${JSON.stringify(validatedConfig)}'`);\n\n  /*\n  TODO: do we want to load all available local templates or only the included ones in the instance?\n  Example: we have a local template folder which we can always traverse. So we could load every CF defined there.\n  But then we could also have in theory conflicted CF IDs if user want to define same CF in different templates.\n  How to handle overwrite? Maybe also support overriding CFs defined in Trash or something?\n  */\n  // const localTemplateCFDs = Array.from(localTemplateMap.values()).reduce((p, c) => {\n  //   if (c.customFormatDefinitions) {\n  //     p.push(...c.customFormatDefinitions);\n  //   }\n  //   return p;\n  // }, [] as CustomFormatDefinitions);\n\n  return { config: validatedConfig };\n};\n\nconst mapConfigMediaNamingToApi = async (arrType: ArrType, mediaNaming: MediaNamingType): Promise<any | null> => {\n  if (arrType === \"RADARR\") {\n    const trashNaming = await loadNamingFromTrashRadarr();\n\n    if (trashNaming == null) {\n      return null;\n    }\n\n    const folderFormat = mediaNamingToApiWithLog(\"RADARR\", mediaNaming.folder, trashNaming.folder, \"mediaNaming.folder\");\n    const standardFormat = mediaNamingToApiWithLog(\"RADARR\", mediaNaming.movie?.standard, trashNaming.file, \"mediaNaming.movie.standard\");\n\n    const apiObject: RadarrNamingConfigResource = {\n      ...(folderFormat && { movieFolderFormat: folderFormat }),\n      ...(standardFormat && { standardMovieFormat: standardFormat }),\n      ...(mediaNaming.movie?.rename != null && { renameMovies: mediaNaming.movie?.rename === true }),\n    };\n\n    logger.debug(apiObject, `Mapped mediaNaming to API:`);\n    return apiObject;\n  }\n\n  if (arrType === \"SONARR\") {\n    const trashNaming = await loadNamingFromTrashSonarr();\n\n    if (trashNaming == null) {\n      return null;\n    }\n\n    const seriesFormat = mediaNamingToApiWithLog(\"SONARR\", mediaNaming.series, trashNaming.series, \"mediaNaming.series\");\n    const seasonsFormat = mediaNamingToApiWithLog(\"SONARR\", mediaNaming.season, trashNaming.season, \"mediaNaming.season\");\n    const standardFormat = mediaNamingToApiWithLog(\n      \"SONARR\",\n      mediaNaming.episodes?.standard,\n      trashNaming.episodes.standard,\n      \"mediaNaming.episodes.standard\",\n    );\n    const dailyFormat = mediaNamingToApiWithLog(\n      \"SONARR\",\n      mediaNaming.episodes?.daily,\n      trashNaming.episodes.daily,\n      \"mediaNaming.episodes.daily\",\n    );\n    const animeFormat = mediaNamingToApiWithLog(\n      \"SONARR\",\n      mediaNaming.episodes?.anime,\n      trashNaming.episodes.anime,\n      \"mediaNaming.episodes.anime\",\n    );\n\n    const apiObject: SonarrNamingConfigResource = {\n      ...(seriesFormat && { seriesFolderFormat: seriesFormat }),\n      ...(seasonsFormat && { seasonFolderFormat: seasonsFormat }),\n      ...(standardFormat && { standardEpisodeFormat: standardFormat }),\n      ...(dailyFormat && { dailyEpisodeFormat: dailyFormat }),\n      ...(animeFormat && { animeEpisodeFormat: animeFormat }),\n      ...(mediaNaming.episodes?.rename != null && { renameEpisodes: mediaNaming.episodes?.rename === true }),\n    };\n\n    logger.debug(apiObject, `Mapped mediaNaming to API:`);\n\n    return apiObject;\n  }\n\n  logger.warn(`MediaNaming not supported for ${arrType}`);\n};\n\nconst mediaNamingToApiWithLog = (arrType: ArrType, key: string | undefined, trashObject: any, label: string) => {\n  if (key) {\n    if (trashObject[key] == null) {\n      logger.warn(`(${arrType}) Specified ${label} '${key}' could not be found in TRaSH-Guide. Check debug logs for available keys.`);\n    } else {\n      return trashObject[key];\n    }\n  }\n\n  return null;\n};\n"
  },
  {
    "path": "src/custom-formats.test.ts",
    "content": "import fs from \"node:fs\";\nimport { beforeEach, describe, expect, it, vi } from \"vitest\";\nimport * as unifiedClient from \"./clients/unified-client\";\nimport * as config from \"./config\";\nimport * as env from \"./env\";\nimport { calculateCFsToManage, loadCustomFormatDefinitions, loadLocalCfs, manageCf, mergeCfSources } from \"./custom-formats\";\nimport { loadTrashCFs } from \"./trash-guide\";\nimport { CFIDToConfigGroup, CFProcessing, ConfigarrCF } from \"./types/common.types\";\nimport { ConfigCustomFormatList } from \"./types/config.types\";\nimport { MergedCustomFormatResource } from \"./types/merged.types\";\nimport { TrashCF } from \"./types/trashguide.types\";\nimport * as util from \"./util\";\nimport { logger } from \"./logger\";\n\ndescribe(\"CustomFormats\", () => {\n  let customCF: TrashCF;\n\n  beforeEach(() => {\n    vi.resetAllMocks();\n    vi.mock(\"node:fs\");\n\n    customCF = {\n      trash_id: \"custom-size-more-40gb\",\n      trash_scores: {\n        default: -10000,\n      },\n      trash_description: \"Size: Block sizes over 40GB\",\n      name: \"Size: Block More 40GB\",\n      includeCustomFormatWhenRenaming: false,\n      specifications: [\n        {\n          name: \"Size\",\n          implementation: \"SizeSpecification\",\n          negate: false,\n          required: true,\n          fields: {\n            min: 1,\n            max: 9,\n          },\n        },\n      ],\n    };\n  });\n\n  describe(\"loadLocalCfs\", () => {\n    it(\"should return null when no local path is configured\", async () => {\n      vi.spyOn(config, \"getConfig\").mockReturnValue({ localCustomFormatsPath: undefined });\n\n      const result = await loadLocalCfs();\n      expect(result.size).toBe(0);\n    });\n\n    it(\"should return null when configured path doesn't exist\", async () => {\n      vi.spyOn(config, \"getConfig\").mockReturnValue({ localCustomFormatsPath: \"/fake/path\" });\n      vi.spyOn(fs, \"existsSync\").mockReturnValue(false);\n\n      const result = await loadLocalCfs();\n      expect(result.size).toBe(0);\n    });\n\n    it(\"should load and process JSON files from configured path\", async () => {\n      vi.spyOn(config, \"getConfig\").mockReturnValue({ localCustomFormatsPath: \"/valid/path\" });\n      vi.spyOn(fs, \"existsSync\").mockReturnValue(true);\n\n      vi.spyOn(fs, \"readdirSync\").mockReturnValue([\"test.json\"] as any);\n      //vi.spyOn(fs, \"readFileSync\").mockImplementationOnce(() => \"{}\");\n\n      vi.spyOn(util, \"loadJsonFile\").mockReturnValueOnce(customCF);\n\n      const result = await loadLocalCfs();\n      expect(result).not.toBeNull();\n      expect(result.size).toBe(1);\n      expect(result.get(customCF.trash_id)).not.toBeNull();\n    });\n  });\n\n  describe(\"mergeCfSources\", () => {\n    const mkCfPair = (id: string, name: string, specCount: number) => {\n      const specifications = Array.from({ length: specCount }, (_, i) => ({\n        name: `S${i}`,\n        implementation: \"ReleaseGroupSpecification\" as const,\n        negate: false,\n        required: false,\n        fields: { value: `^(${i})$` },\n      }));\n      const carrConfig = { configarr_id: id, name, specifications } as unknown as ConfigarrCF;\n      return { carrConfig, requestConfig: util.mapImportCfToRequestCf(carrConfig) };\n    };\n\n    it(\"should merge multiple CF sources correctly\", () => {\n      const source1: CFIDToConfigGroup = new Map([[\"id1\", { carrConfig: { configarr_id: \"id1\", name: \"CF1\" }, requestConfig: {} }]]);\n\n      const source2: CFIDToConfigGroup = new Map([[\"id2\", { carrConfig: { configarr_id: \"id2\", name: \"CF2\" }, requestConfig: {} }]]);\n\n      const result = mergeCfSources(new Set([\"id1\", \"id2\"]), [source1, source2, null]);\n\n      expect(result.carrIdMapping.size).toBe(2);\n      expect(result.cfNameToCarrConfig.size).toBe(2);\n      expect(result.carrIdMapping.has(\"id1\")).toBeTruthy();\n      expect(result.carrIdMapping.has(\"id2\")).toBeTruthy();\n    });\n\n    it(\"should keep one cfNameToCarrConfig winner when two trash_ids share the same CF name\", () => {\n      const first = mkCfPair(\"id-a\", \"Dup\", 3);\n      const second = mkCfPair(\"id-b\", \"Dup\", 1);\n      const source: CFIDToConfigGroup = new Map([\n        [\"id-a\", { carrConfig: first.carrConfig, requestConfig: first.requestConfig }],\n        [\"id-b\", { carrConfig: second.carrConfig, requestConfig: second.requestConfig }],\n      ]);\n\n      const result = mergeCfSources(new Set([\"id-a\", \"id-b\"]), [source, null]);\n\n      expect(result.carrIdMapping.size).toBe(2);\n      expect(result.cfNameToCarrConfig.size).toBe(1);\n      expect(result.cfNameToCarrConfig.get(\"Dup\")?.configarr_id).toBe(\"id-b\");\n      expect(result.cfNameToCarrConfig.get(\"Dup\")?.specifications?.length).toBe(1);\n      const winnerCarr = result.cfNameToCarrConfig.get(\"Dup\")!;\n      const idB = result.carrIdMapping.get(\"id-b\")!;\n      expect(util.compareCustomFormats(util.mapImportCfToRequestCf(winnerCarr), idB.requestConfig).equal).toBe(true);\n    });\n\n    it(\"should warn when two trash_ids share a name but have different specifications\", () => {\n      const warnSpy = vi.spyOn(logger, \"warn\").mockImplementation(() => {});\n\n      const first = mkCfPair(\"id-a\", \"Dup\", 2);\n      const second = mkCfPair(\"id-b\", \"Dup\", 1);\n      const source: CFIDToConfigGroup = new Map([\n        [\"id-a\", { carrConfig: first.carrConfig, requestConfig: first.requestConfig }],\n        [\"id-b\", { carrConfig: second.carrConfig, requestConfig: second.requestConfig }],\n      ]);\n\n      mergeCfSources(new Set([\"id-a\", \"id-b\"]), [source, null]);\n\n      expect(warnSpy).toHaveBeenCalledWith(expect.stringMatching(/trash_id 'id-b' wins over 'id-a'.*Sync uses 'id-b'/));\n      warnSpy.mockRestore();\n    });\n  });\n\n  describe(\"calculateCFsToManage\", () => {\n    it(\"should collect all trash IDs from custom format list\", () => {\n      const yaml: ConfigCustomFormatList = {\n        custom_formats: [\n          { trash_ids: [\"t1\", \"t2\"], assign_scores_to: [{ name: \"default\", score: 100 }] },\n          { trash_ids: [\"t3\"], assign_scores_to: [{ name: \"default\", score: 100 }] },\n          { trash_ids: [\"t2\"], assign_scores_to: [{ name: \"default\", score: 100 }] }, // Duplicate to test Set behavior\n        ],\n      };\n\n      const result = calculateCFsToManage(yaml);\n\n      expect(result.size).toBe(3);\n      expect(result.has(\"t1\")).toBeTruthy();\n      expect(result.has(\"t2\")).toBeTruthy();\n      expect(result.has(\"t3\")).toBeTruthy();\n    });\n  });\n\n  describe(\"loadCustomFormatDefinitions\", () => {\n    it(\"should load and merge (trash CFDs\", async () => {\n      const mockTrashCFs: CFIDToConfigGroup = new Map([\n        [\"trash1\", { carrConfig: { configarr_id: \"trash1\", name: \"trash1\" }, requestConfig: {} }],\n      ]);\n\n      vi.mock(\"./trash-guide\");\n      vi.mocked(loadTrashCFs).mockResolvedValue(mockTrashCFs);\n      vi.spyOn(config, \"getConfig\").mockReturnValue({ localCustomFormatsPath: undefined });\n\n      const result = await loadCustomFormatDefinitions(new Set([\"trash1\"]), \"RADARR\", []);\n\n      expect(result.carrIdMapping.size).toBe(1);\n      expect(result.carrIdMapping.has(\"trash1\")).toBeTruthy();\n    });\n\n    it(\"should load and merge (additional CFDs)\", async () => {\n      const mockTrashCFs: CFIDToConfigGroup = new Map([\n        [\"trash1\", { carrConfig: { configarr_id: \"trash1\", name: \"trash1\" }, requestConfig: {} }],\n      ]);\n\n      vi.mock(\"./trash-guide\");\n      vi.mocked(loadTrashCFs).mockResolvedValue(mockTrashCFs);\n      vi.spyOn(config, \"getConfig\").mockReturnValue({ localCustomFormatsPath: undefined });\n\n      const result = await loadCustomFormatDefinitions(new Set([\"trash1\", customCF.trash_id]), \"RADARR\", [customCF]);\n\n      expect(result.carrIdMapping.size).toBe(2);\n      expect(result.carrIdMapping.has(\"trash1\")).toBeTruthy();\n    });\n\n    it(\"should ignore not managed CFs\", async () => {\n      const mockTrashCFs: CFIDToConfigGroup = new Map([\n        [\"trash1\", { carrConfig: { configarr_id: \"trash1\", name: \"trash1\" }, requestConfig: {} }],\n      ]);\n\n      vi.mock(\"./trash-guide\");\n      vi.mocked(loadTrashCFs).mockResolvedValue(mockTrashCFs);\n      vi.spyOn(config, \"getConfig\").mockReturnValue({ localCustomFormatsPath: undefined });\n\n      const result = await loadCustomFormatDefinitions(new Set([\"trash1\"]), \"RADARR\", [customCF]);\n\n      expect(result.carrIdMapping.size).toBe(1);\n      expect(result.carrIdMapping.has(\"trash1\")).toBeTruthy();\n    });\n  });\n\n  describe(\"manageCf\", () => {\n    it(\"should update each CF name at most once when server already matches desired state\", async () => {\n      vi.spyOn(env, \"getEnvs\").mockReturnValue({\n        DRY_RUN: false,\n      } as ReturnType<typeof env.getEnvs>);\n\n      const specifications = [\n        {\n          name: \"S0\",\n          implementation: \"ReleaseGroupSpecification\" as const,\n          negate: false,\n          required: false,\n          fields: { value: \"^(0)$\" },\n        },\n      ];\n      const carrConfigB = { configarr_id: \"id-b\", name: \"Dup\", specifications } as unknown as ConfigarrCF;\n      const requestConfigB = util.mapImportCfToRequestCf(carrConfigB);\n      const carrConfigA = {\n        configarr_id: \"id-a\",\n        name: \"Dup\",\n        specifications: [...specifications, { ...specifications[0], name: \"S1\" }],\n      } as unknown as ConfigarrCF;\n      const requestConfigA = util.mapImportCfToRequestCf(carrConfigA);\n\n      const cfProcessing: CFProcessing = {\n        carrIdMapping: new Map([\n          [\"id-a\", { carrConfig: carrConfigA, requestConfig: requestConfigA }],\n          [\"id-b\", { carrConfig: carrConfigB, requestConfig: requestConfigB }],\n        ]),\n        cfNameToCarrConfig: new Map([[carrConfigB.name!, carrConfigB]]),\n      };\n\n      const serverCf: MergedCustomFormatResource = { id: 1, name: \"Dup\", ...requestConfigB };\n      const serverCfs = new Map<string, MergedCustomFormatResource>([[\"Dup\", serverCf]]);\n\n      const updateCustomFormat = vi.fn();\n      vi.spyOn(unifiedClient, \"getUnifiedClient\").mockReturnValue({\n        updateCustomFormat,\n        createCustomFormat: vi.fn(),\n      } as unknown as ReturnType<typeof unifiedClient.getUnifiedClient>);\n\n      const out = await manageCf(cfProcessing, serverCfs);\n\n      expect(updateCustomFormat).not.toHaveBeenCalled();\n      expect(out.errorCFs.length).toBe(0);\n    });\n\n    it(\"should apply a single update per CF name using cfNameToCarrConfig winner when server matches a non-winner trash_id body\", async () => {\n      vi.spyOn(env, \"getEnvs\").mockReturnValue({ DRY_RUN: false } as ReturnType<typeof env.getEnvs>);\n\n      const specifications = [\n        {\n          name: \"S0\",\n          implementation: \"ReleaseGroupSpecification\" as const,\n          negate: false,\n          required: false,\n          fields: { value: \"^(0)$\" },\n        },\n      ];\n      const carrConfigB = { configarr_id: \"id-b\", name: \"Dup\", specifications } as unknown as ConfigarrCF;\n      const requestConfigB = util.mapImportCfToRequestCf(carrConfigB);\n      const carrConfigA = {\n        configarr_id: \"id-a\",\n        name: \"Dup\",\n        specifications: [...specifications, { ...specifications[0], name: \"S1\" }],\n      } as unknown as ConfigarrCF;\n      const requestConfigA = util.mapImportCfToRequestCf(carrConfigA);\n\n      const cfProcessing: CFProcessing = {\n        carrIdMapping: new Map([\n          [\"id-a\", { carrConfig: carrConfigA, requestConfig: requestConfigA }],\n          [\"id-b\", { carrConfig: carrConfigB, requestConfig: requestConfigB }],\n        ]),\n        cfNameToCarrConfig: new Map([[carrConfigB.name!, carrConfigB]]),\n      };\n\n      const serverCfStale: MergedCustomFormatResource = { id: 1, name: \"Dup\", ...requestConfigA };\n      const serverCfs = new Map<string, MergedCustomFormatResource>([[\"Dup\", serverCfStale]]);\n\n      const updateCustomFormat = vi.fn().mockResolvedValue({ id: 1, name: \"Dup\", ...requestConfigB });\n      vi.spyOn(unifiedClient, \"getUnifiedClient\").mockReturnValue({\n        updateCustomFormat,\n        createCustomFormat: vi.fn(),\n      } as unknown as ReturnType<typeof unifiedClient.getUnifiedClient>);\n\n      await manageCf(cfProcessing, serverCfs);\n\n      expect(updateCustomFormat).toHaveBeenCalledTimes(1);\n      const updatePayload = updateCustomFormat.mock.calls[0]?.[1];\n      expect(updatePayload).toBeDefined();\n      expect(updatePayload).toMatchObject({ ...requestConfigB });\n    });\n  });\n});\n"
  },
  {
    "path": "src/custom-formats.ts",
    "content": "import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { MergedCustomFormatResource } from \"./types/merged.types\";\nimport { getUnifiedClient } from \"./clients/unified-client\";\nimport { getConfig } from \"./config\";\nimport { getEnvs } from \"./env\";\nimport { logger } from \"./logger\";\nimport { loadTrashCFs } from \"./trash-guide\";\nimport { ArrType, CFIDToConfigGroup, CFProcessing, ConfigarrCF } from \"./types/common.types\";\nimport { ConfigCustomFormatList, CustomFormatDefinitions } from \"./types/config.types\";\nimport { TrashCF } from \"./types/trashguide.types\";\nimport { compareCustomFormats, loadJsonFile, mapImportCfToRequestCf, toCarrCF } from \"./util\";\n\nexport const deleteAllCustomFormats = async () => {\n  const api = getUnifiedClient();\n  const cfOnServer = await api.getCustomFormats();\n\n  for (const cf of cfOnServer) {\n    await api.deleteCustomFormat(cf.id + \"\");\n    logger.info(`Deleted CF: '${cf.name}'`);\n  }\n};\n\nexport const deleteCustomFormat = async (customFormat: MergedCustomFormatResource) => {\n  const api = getUnifiedClient();\n\n  await api.deleteCustomFormat(customFormat.id + \"\");\n  logger.info(`Deleted CF: '${customFormat.name}'`);\n};\n\nexport const loadServerCustomFormats = async (): Promise<MergedCustomFormatResource[]> => {\n  if (getEnvs().LOAD_LOCAL_SAMPLES) {\n    return loadJsonFile<MergedCustomFormatResource[]>(path.resolve(__dirname, \"../tests/samples/cfs.json\"));\n  }\n  const api = getUnifiedClient();\n  const cfOnServer = await api.getCustomFormats();\n  return cfOnServer;\n};\n\nexport const manageCf = async (cfProcessing: CFProcessing, serverCfs: Map<string, MergedCustomFormatResource>) => {\n  const { cfNameToCarrConfig } = cfProcessing;\n  const api = getUnifiedClient();\n\n  let updatedCFs: MergedCustomFormatResource[] = [];\n  let errorCFs: string[] = [];\n  const validCFs: ConfigarrCF[] = [];\n  let createCFs: MergedCustomFormatResource[] = [];\n\n  const manageSingle = async (cfName: string, carrConfig: ConfigarrCF) => {\n    const requestConfig = mapImportCfToRequestCf(carrConfig);\n    const existingCf = serverCfs.get(cfName);\n\n    if (existingCf) {\n      // Update if necessary\n      const comparison = compareCustomFormats(existingCf, requestConfig);\n\n      if (!comparison.equal) {\n        logger.debug(`Found mismatch for ${requestConfig.name}: ${comparison.changes}`);\n\n        try {\n          if (getEnvs().DRY_RUN) {\n            logger.info(`DryRun: Would update CF: ${existingCf.id} - ${existingCf.name}`);\n            updatedCFs.push(existingCf);\n          } else {\n            const updatedCf = await api.updateCustomFormat(existingCf.id + \"\", {\n              id: existingCf.id,\n              ...requestConfig,\n            });\n            logger.debug(`Updated CF ${requestConfig.name}`);\n            updatedCFs.push(updatedCf);\n          }\n        } catch (err: any) {\n          const data = err?.response?.data;\n          const dataMessage = typeof data === \"object\" ? (data?.message ?? data?.errorMessage) : data;\n          const errorMessage = dataMessage ?? err?.message ?? String(err);\n          logger.error(errorMessage, `Failed updating CF ${requestConfig.name}`);\n          errorCFs.push(carrConfig.configarr_id ?? requestConfig.name ?? \"unknown\");\n          throw new Error(`Failed updating CF '${requestConfig.name}'. Message: ${errorMessage}`, { cause: err });\n        }\n      } else {\n        validCFs.push(carrConfig);\n      }\n    } else {\n      // Create\n      try {\n        if (getEnvs().DRY_RUN) {\n          logger.info(`Would create CF: ${requestConfig.name}`);\n        } else {\n          const createResult = await api.createCustomFormat(requestConfig);\n          logger.info(`Created CF ${requestConfig.name}`);\n          createCFs.push(createResult);\n          serverCfs.set(createResult.name!, createResult);\n        }\n      } catch (err: any) {\n        const data = err?.response?.data;\n        const dataMessage = typeof data === \"object\" ? (data?.message ?? data?.errorMessage) : data;\n        const errorMessage = dataMessage ?? err?.message ?? String(err);\n        logger.error(errorMessage, `Failed creating CF ${requestConfig.name}`);\n        errorCFs.push(carrConfig.configarr_id ?? requestConfig.name ?? \"unknown\");\n        throw new Error(`Failed creating CF '${requestConfig.name}'. Message: ${errorMessage}`, { cause: err });\n      }\n    }\n  };\n\n  for (const [cfName, carrConfig] of cfNameToCarrConfig) {\n    await manageSingle(cfName, carrConfig);\n  }\n\n  if (validCFs.length > 0) {\n    logger.debug(\n      validCFs.map((e) => `${e.name}`),\n      `CFs with no update:`,\n    );\n  }\n  logger.info(\n    `Created CFs: ${createCFs.length}, Updated CFs: ${updatedCFs.length}, Untouched CFs: ${validCFs.length}, Error CFs: ${errorCFs.length}`,\n  );\n\n  return { createCFs, updatedCFs, validCFs, errorCFs };\n};\n\nexport const loadLocalCfs = async (): Promise<CFIDToConfigGroup> => {\n  const config = getConfig();\n  const carrIdToObject = new Map<string, { carrConfig: ConfigarrCF; requestConfig: MergedCustomFormatResource }>();\n\n  if (config.localCustomFormatsPath == null) {\n    logger.debug(`No local custom formats specified. Skipping.`);\n    return carrIdToObject;\n  }\n\n  const cfPath = path.resolve(config.localCustomFormatsPath);\n\n  if (!fs.existsSync(cfPath)) {\n    logger.info(`Provided local custom formats path '${config.localCustomFormatsPath}' does not exist.`);\n    return carrIdToObject;\n  }\n\n  const files = fs.readdirSync(`${cfPath}`).filter((fn) => fn.endsWith(\"json\"));\n\n  for (const file of files) {\n    const name = `${cfPath}/${file}`;\n    const cf = loadJsonFile<TrashCF | ConfigarrCF>(path.resolve(name));\n\n    const cfD = toCarrCF(cf);\n\n    carrIdToObject.set(cfD.configarr_id, {\n      carrConfig: cfD,\n      requestConfig: mapImportCfToRequestCf(cfD),\n    });\n  }\n\n  return carrIdToObject;\n};\n\nexport const loadCFFromConfig = (): CFIDToConfigGroup | null => {\n  const defs = getConfig().customFormatDefinitions;\n\n  if (defs == null) {\n    logger.debug(`No local config CustomFormat definitions defined.`);\n    return null;\n  }\n\n  return mapCustomFormatDefinitions(defs);\n};\n\nexport const mapCustomFormatDefinitions = (customFormatDefinitions: CustomFormatDefinitions): CFIDToConfigGroup | null => {\n  if (customFormatDefinitions == null) {\n    return null;\n  }\n\n  const carrIdToObject = new Map<string, { carrConfig: ConfigarrCF; requestConfig: MergedCustomFormatResource }>();\n\n  for (const def of customFormatDefinitions) {\n    const cfD = toCarrCF(def);\n\n    if (carrIdToObject.has(cfD.configarr_id)) {\n      logger.warn(`Duplicate ConfigCF ID found: '${cfD.configarr_id}'. Overwriting with name '${cfD.name}'`);\n    }\n\n    carrIdToObject.set(cfD.configarr_id, {\n      carrConfig: cfD,\n      requestConfig: mapImportCfToRequestCf(cfD),\n    });\n  }\n\n  return carrIdToObject;\n};\n\nexport const loadCustomFormatDefinitions = async (idsToMange: Set<string>, arrType: ArrType, additionalCFDs: CustomFormatDefinitions) => {\n  let trashCFs: CFIDToConfigGroup = new Map();\n\n  if (arrType === \"RADARR\" || arrType === \"SONARR\") {\n    trashCFs = await loadTrashCFs(arrType);\n  }\n\n  const localFileCFs = await loadLocalCfs();\n\n  logger.debug(`Total loaded CF definitions: ${trashCFs.size} TrashCFs, ${localFileCFs.size} LocalCFs, ${additionalCFDs.length} ConfigCFs`);\n\n  return mergeCfSources(idsToMange, [trashCFs, localFileCFs, mapCustomFormatDefinitions(additionalCFDs)]);\n};\n\nexport const calculateCFsToManage = (yaml: ConfigCustomFormatList) => {\n  const cfTrashToManage: Set<string> = new Set();\n\n  yaml.custom_formats.map((cf) => {\n    if (cf.trash_ids) {\n      cf.trash_ids.forEach((tid) => cfTrashToManage.add(tid));\n    }\n  });\n\n  return cfTrashToManage;\n};\n\nexport const mergeCfSources = (idsToManage: Set<string>, listOfCfs: (CFIDToConfigGroup | null)[]): CFProcessing => {\n  const lastTrashIdByCfName = new Map<string, string>();\n\n  return listOfCfs.reduce<CFProcessing>(\n    (p, c) => {\n      if (c == null) {\n        return p;\n      }\n\n      for (const test of idsToManage) {\n        const value = c.get(test);\n\n        if (value) {\n          const cfName = value.carrConfig.name!;\n          if (p.carrIdMapping.has(test)) {\n            logger.warn(`Overwriting CF with id '${test}' during merge.`);\n          }\n\n          if (p.cfNameToCarrConfig.has(cfName)) {\n            const prevCarr = p.cfNameToCarrConfig.get(cfName)!;\n            const prevTid = lastTrashIdByCfName.get(cfName)!;\n            const specsDiffer = !compareCustomFormats(mapImportCfToRequestCf(prevCarr), value.requestConfig).equal;\n            const specNote = specsDiffer ? \" Definitions for those ids are not identical;\" : \"\";\n            logger.warn(\n              `Overwriting CF with name '${cfName}': trash_id '${test}' wins over '${prevTid}' (later merge order).${specNote} Sync uses '${test}'.`,\n            );\n          }\n\n          p.carrIdMapping.set(test, value);\n          p.cfNameToCarrConfig.set(cfName, value.carrConfig);\n          lastTrashIdByCfName.set(cfName, test);\n        }\n      }\n\n      return p;\n    },\n    {\n      carrIdMapping: new Map(),\n      cfNameToCarrConfig: new Map(),\n    },\n  );\n};\n"
  },
  {
    "path": "src/delay-profiles.test.ts",
    "content": "import { describe, expect, test, vi } from \"vitest\";\nimport { MergedDelayProfileResource } from \"./types/merged.types\";\n\n// Hoist the mock to ensure it runs before imports\nconst mockGetDelayProfiles = vi.hoisted(() => vi.fn());\n\nvi.mock(\"./clients/unified-client\", () => ({\n  getUnifiedClient: () => ({\n    getDelayProfiles: mockGetDelayProfiles,\n  }),\n}));\n\ndescribe(\"DelayProfiles\", () => {\n  test(\"should not diff (with default profile and additional profile)\", async () => {\n    const configProfiles = {\n      default: {\n        enableUsenet: true,\n        enableTorrent: false,\n        preferredProtocol: \"usenet\",\n        usenetDelay: 10,\n        torrentDelay: 0,\n        bypassIfHighestQuality: false,\n        bypassIfAboveCustomFormatScore: false,\n        minimumCustomFormatScore: 0,\n        order: 1,\n      },\n      additional: [\n        {\n          enableUsenet: true,\n          enableTorrent: false,\n          preferredProtocol: \"usenet\",\n          usenetDelay: 10,\n          torrentDelay: 0,\n          bypassIfHighestQuality: false,\n          bypassIfAboveCustomFormatScore: false,\n          minimumCustomFormatScore: 0,\n          order: 2,\n          tags: [\"test\"],\n        },\n      ],\n    };\n\n    // Simulate server data that matches the config\n    const serverProfiles: MergedDelayProfileResource[] = [\n      {\n        enableUsenet: true,\n        enableTorrent: false,\n        preferredProtocol: \"usenet\" as any,\n        usenetDelay: 10,\n        torrentDelay: 0,\n        bypassIfHighestQuality: false,\n        bypassIfAboveCustomFormatScore: false,\n        minimumCustomFormatScore: 0,\n        order: 1,\n        tags: [], // default profile\n      },\n      {\n        id: 1,\n        enableUsenet: true,\n        enableTorrent: false,\n        preferredProtocol: \"usenet\" as any,\n        usenetDelay: 10,\n        torrentDelay: 0,\n        bypassIfHighestQuality: false,\n        bypassIfAboveCustomFormatScore: false,\n        minimumCustomFormatScore: 0,\n        order: 2,\n        tags: [1], // matches \"test\" tag\n      },\n    ];\n\n    mockGetDelayProfiles.mockResolvedValue(serverProfiles);\n\n    const { calculateDelayProfilesDiff } = await import(\"./delay-profiles\");\n    const diff = await calculateDelayProfilesDiff(configProfiles, [{ label: \"test\", id: 1 }]);\n\n    expect(diff).toBeNull();\n  });\n\n  test(\"should diff - changes in default profile\", async () => {\n    const configProfiles = {\n      default: {\n        enableUsenet: false,\n        enableTorrent: true,\n        preferredProtocol: \"torrent\",\n        usenetDelay: 0,\n        torrentDelay: 15,\n        bypassIfHighestQuality: true,\n        bypassIfAboveCustomFormatScore: false,\n        minimumCustomFormatScore: 0,\n      },\n    };\n\n    // Simulate server data with different default profile\n    const serverProfiles: MergedDelayProfileResource[] = [\n      {\n        enableUsenet: true,\n        enableTorrent: false,\n        preferredProtocol: \"usenet\" as any,\n        usenetDelay: 10,\n        torrentDelay: 0,\n        bypassIfHighestQuality: false,\n        bypassIfAboveCustomFormatScore: false,\n        minimumCustomFormatScore: 0,\n        order: 1,\n        tags: [], // default profile\n      },\n    ];\n\n    mockGetDelayProfiles.mockResolvedValue(serverProfiles);\n\n    const { calculateDelayProfilesDiff } = await import(\"./delay-profiles\");\n    const diff = await calculateDelayProfilesDiff(configProfiles, []);\n\n    expect(diff).not.toBeNull();\n    expect(diff?.defaultProfileChanged).toBe(true);\n    expect(diff?.additionalProfilesChanged).toBe(false);\n    expect(diff?.defaultProfile).toBeDefined();\n    expect(diff?.additionalProfiles).toHaveLength(0);\n    expect(diff?.missingTags).toHaveLength(0);\n  });\n\n  test(\"should diff - changes in additional profile\", async () => {\n    const configProfiles = {\n      additional: [\n        {\n          enableUsenet: false,\n          enableTorrent: true,\n          preferredProtocol: \"torrent\",\n          usenetDelay: 0,\n          torrentDelay: 20,\n          bypassIfHighestQuality: true,\n          bypassIfAboveCustomFormatScore: false,\n          minimumCustomFormatScore: 0,\n          order: 2,\n          tags: [\"test\"],\n        },\n      ],\n    };\n    // Simulate server data\n    const serverProfiles: MergedDelayProfileResource[] = [\n      {\n        enableUsenet: true,\n        enableTorrent: false,\n        preferredProtocol: \"usenet\" as any,\n        usenetDelay: 10,\n        torrentDelay: 0,\n        bypassIfHighestQuality: false,\n        bypassIfAboveCustomFormatScore: false,\n        minimumCustomFormatScore: 0,\n        order: 1,\n        tags: [], // default profile\n      },\n      {\n        id: 1,\n        enableUsenet: true,\n        enableTorrent: false,\n        preferredProtocol: \"usenet\" as any,\n        usenetDelay: 10,\n        torrentDelay: 0,\n        bypassIfHighestQuality: false,\n        bypassIfAboveCustomFormatScore: false,\n        minimumCustomFormatScore: 0,\n        order: 1,\n        tags: [1, 2],\n      },\n    ];\n\n    mockGetDelayProfiles.mockResolvedValue(serverProfiles);\n\n    const { calculateDelayProfilesDiff } = await import(\"./delay-profiles\");\n    const diff = await calculateDelayProfilesDiff(configProfiles, []);\n\n    expect(diff).not.toBeNull();\n    expect(diff?.defaultProfileChanged).toBe(false);\n    expect(diff?.additionalProfilesChanged).toBe(true);\n    expect(diff?.defaultProfile).not.toBeDefined();\n    expect(diff?.additionalProfiles).toBeDefined();\n    expect(diff?.missingTags).toHaveLength(1);\n  });\n\n  test(\"should require new tags to be created\", async () => {\n    const configProfiles = {\n      additional: [\n        {\n          enableUsenet: false,\n          enableTorrent: true,\n          preferredProtocol: \"torrent\",\n          usenetDelay: 0,\n          torrentDelay: 20,\n          bypassIfHighestQuality: true,\n          bypassIfAboveCustomFormatScore: false,\n          minimumCustomFormatScore: 0,\n          order: 2,\n          tags: [\"test\"],\n        },\n      ],\n    };\n    // Simulate server data\n    const serverProfiles: MergedDelayProfileResource[] = [\n      {\n        enableUsenet: true,\n        enableTorrent: false,\n        preferredProtocol: \"usenet\" as any,\n        usenetDelay: 10,\n        torrentDelay: 0,\n        bypassIfHighestQuality: false,\n        bypassIfAboveCustomFormatScore: false,\n        minimumCustomFormatScore: 0,\n        order: 1,\n        tags: [], // default profile\n      },\n      {\n        id: 1,\n        enableUsenet: true,\n        enableTorrent: false,\n        preferredProtocol: \"usenet\" as any,\n        usenetDelay: 10,\n        torrentDelay: 0,\n        bypassIfHighestQuality: false,\n        bypassIfAboveCustomFormatScore: false,\n        minimumCustomFormatScore: 0,\n        order: 1,\n        tags: [1, 2],\n      },\n    ];\n\n    mockGetDelayProfiles.mockResolvedValue(serverProfiles);\n\n    const { calculateDelayProfilesDiff } = await import(\"./delay-profiles\");\n    const diff = await calculateDelayProfilesDiff(configProfiles, []);\n\n    expect(diff).not.toBeNull();\n    expect(diff?.missingTags).toHaveLength(1);\n  });\n});\n"
  },
  {
    "path": "src/delay-profiles.ts",
    "content": "import { getUnifiedClient } from \"./clients/unified-client\";\nimport { logger } from \"./logger\";\nimport { InputConfigDelayProfile } from \"./types/config.types\";\nimport { MergedDelayProfileResource, MergedTagResource } from \"./types/merged.types\";\n\nexport const deleteAdditionalDelayProfiles = async () => {\n  const api = getUnifiedClient();\n\n  const serverData: MergedDelayProfileResource[] = await api.getDelayProfiles();\n  const { additional: serverAdditional = [] } = splitServerDelayProfiles(serverData);\n\n  for (const p of serverAdditional) {\n    await api.deleteDelayProfile(p.id + \"\");\n    logger.info(`Deleted Delay Profile: '${p.id}'`);\n  }\n};\n\n// Helper to flatten delay profiles (default + additional) to a single array\nexport function flattenDelayProfiles<T extends { tags?: number[] | null }>(delayProfilesObj: { default?: T; additional?: T[] }): T[] {\n  const arr: T[] = [];\n  if (delayProfilesObj.default) arr.push(delayProfilesObj.default);\n  if (Array.isArray(delayProfilesObj.additional)) arr.push(...delayProfilesObj.additional);\n  return arr;\n}\n\n// Helper to split server delay profiles into default/additional\nexport function splitServerDelayProfiles(serverProfiles: MergedDelayProfileResource[]): {\n  default?: MergedDelayProfileResource;\n  additional?: MergedDelayProfileResource[];\n} {\n  let defaultProfile: MergedDelayProfileResource | undefined = undefined;\n  const additional: MergedDelayProfileResource[] = [];\n  for (const p of serverProfiles) {\n    if (!Array.isArray(p.tags) || p.tags.length === 0) {\n      defaultProfile = p;\n    } else {\n      additional.push(p);\n    }\n  }\n  return { default: defaultProfile, additional: additional.length > 0 ? additional : undefined };\n}\n\nexport const mapToServerDelayProfile = (profile: InputConfigDelayProfile, serverTags: MergedTagResource[]): MergedDelayProfileResource => {\n  const mappedTags = profile.tags?.map((tagName) => serverTags.find((t) => t.label === tagName)?.id).filter((t) => t !== undefined) || [];\n  return {\n    enableUsenet: profile.enableUsenet,\n    enableTorrent: profile.enableTorrent,\n    preferredProtocol: (profile.preferredProtocol ?? \"usenet\") as any, // Default to usenet if not specified\n    usenetDelay: profile.usenetDelay,\n    torrentDelay: profile.torrentDelay,\n    bypassIfHighestQuality: profile.bypassIfHighestQuality,\n    bypassIfAboveCustomFormatScore: profile.bypassIfAboveCustomFormatScore,\n    minimumCustomFormatScore: profile.minimumCustomFormatScore,\n    order: profile.order,\n    tags: mappedTags,\n  };\n};\n\nexport const calculateDelayProfilesDiff = async (\n  delayProfilesObj: { default?: InputConfigDelayProfile; additional?: InputConfigDelayProfile[] },\n  tags: MergedTagResource[],\n): Promise<{\n  defaultProfileChanged: boolean;\n  additionalProfilesChanged: boolean;\n  missingTags: string[];\n  defaultProfile?: InputConfigDelayProfile;\n  additionalProfiles?: InputConfigDelayProfile[];\n} | null> => {\n  const { default: configDefault, additional: configAdditional = [] } = delayProfilesObj;\n\n  if (!configDefault && configAdditional.length === 0) {\n    logger.debug(`Config 'delay_profiles' not specified. Ignoring.`);\n    return null;\n  }\n\n  const api = getUnifiedClient();\n  const serverData: MergedDelayProfileResource[] = await api.getDelayProfiles();\n  const { default: serverDefault, additional: serverAdditional = [] } = splitServerDelayProfiles(serverData);\n\n  // Check default profile (no tag comparison for default)\n  const defaultProfileChanged = configDefault && serverDefault ? isDefaultProfileDifferent(configDefault, serverDefault) : false;\n\n  let additionalProfilesChanged = configAdditional.length !== serverAdditional.length;\n\n  if (!additionalProfilesChanged && configAdditional.length > 0) {\n    additionalProfilesChanged = configAdditional.some((config, i) => {\n      const mappedTags = config.tags?.map((tagName) => tags.find((t) => t.label === tagName)?.id).filter((t) => t !== undefined);\n      const serverProfile = serverAdditional[i];\n\n      if (!serverProfile) {\n        logger.debug(`Server profile at index ${i} does not exist.`);\n        return true; // Mark as changed\n      }\n\n      return isProfileDifferent(config, serverProfile, mappedTags || []);\n    });\n  }\n\n  if (!defaultProfileChanged && !additionalProfilesChanged) {\n    logger.debug(`Delay profiles are in sync`);\n    return null;\n  }\n\n  logger.info(`DelayProfiles changes detected - default: ${defaultProfileChanged}, additional: ${additionalProfilesChanged}`);\n\n  const missingTags = configAdditional.flatMap((profile) => {\n    return profile.tags?.filter((tagName) => !tags.some((t) => t.label === tagName)) || [];\n  });\n\n  return {\n    defaultProfileChanged,\n    additionalProfilesChanged,\n    missingTags,\n    defaultProfile: configDefault,\n    additionalProfiles: configAdditional,\n  };\n};\n\n// Helper functions\ntype ComparisonKeys = keyof Pick<\n  InputConfigDelayProfile,\n  | \"enableUsenet\"\n  | \"enableTorrent\"\n  | \"preferredProtocol\"\n  | \"usenetDelay\"\n  | \"torrentDelay\"\n  | \"bypassIfHighestQuality\"\n  | \"bypassIfAboveCustomFormatScore\"\n  | \"minimumCustomFormatScore\"\n  | \"order\"\n>;\n\nconst getProfileTags = (profile: MergedDelayProfileResource): number[] => {\n  return \"tags\" in profile && Array.isArray(profile.tags) ? profile.tags : [];\n};\n\n// Separate function for default profile (no tag comparison)\nconst isDefaultProfileDifferent = (config: InputConfigDelayProfile, server: MergedDelayProfileResource): boolean => {\n  const keys: ComparisonKeys[] = [\n    \"enableUsenet\",\n    \"enableTorrent\",\n    \"preferredProtocol\",\n    \"usenetDelay\",\n    \"torrentDelay\",\n    \"bypassIfHighestQuality\",\n    \"bypassIfAboveCustomFormatScore\",\n    \"minimumCustomFormatScore\",\n    \"order\",\n  ];\n\n  for (const key of keys) {\n    if (config[key] !== undefined && config[key] !== server[key]) {\n      return true;\n    }\n  }\n  return false;\n};\n\n// For additional profiles (includes tag comparison)\nconst isProfileDifferent = (config: InputConfigDelayProfile, server: MergedDelayProfileResource, mappedTags: Array<number>): boolean => {\n  const keys: ComparisonKeys[] = [\n    \"enableUsenet\",\n    \"enableTorrent\",\n    \"preferredProtocol\",\n    \"usenetDelay\",\n    \"torrentDelay\",\n    \"bypassIfHighestQuality\",\n    \"bypassIfAboveCustomFormatScore\",\n    \"minimumCustomFormatScore\",\n    \"order\",\n  ];\n\n  for (const key of keys) {\n    if (config[key] !== undefined && config[key] !== server[key]) {\n      return true;\n    }\n  }\n  if (!areTagsEqual(mappedTags, getProfileTags(server))) {\n    return true;\n  }\n  return false;\n};\n\nconst areTagsEqual = (tags1: number[], tags2: number[]): boolean => {\n  return tags1.length === tags2.length && tags1.sort().join(\",\") === tags2.sort().join(\",\");\n};\n"
  },
  {
    "path": "src/downloadClientConfig/downloadClientConfig.types.ts",
    "content": "/**\n * Download Client Configuration sync types\n * Handles instance-specific configuration for download clients\n */\n\nexport type DownloadClientConfigSyncResult = {\n  updated: boolean;\n  arrType: string;\n};\n"
  },
  {
    "path": "src/downloadClientConfig/downloadClientConfigSyncer.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { syncDownloadClientConfig } from \"./downloadClientConfigSyncer\";\nimport type { ServerCache } from \"../cache\";\nimport type { MergedConfigInstance } from \"../types/config.types\";\nimport type { ArrType } from \"../types/common.types\";\n\n// Mock env - use importOriginal to preserve getHelpers and getBuildInfo\nvi.mock(\"../env\", async (importOriginal) => {\n  const actual = await importOriginal<typeof import(\"../env\")>();\n  return {\n    ...actual,\n    getEnvs: vi.fn(() => ({\n      DRY_RUN: false,\n      LOG_LEVEL: \"silent\",\n      DEBUG_CREATE_FILES: false,\n      CONFIGARR_VERSION: \"test\",\n      ROOT_PATH: \"/tmp/test\",\n    })),\n  };\n});\n\n// Mock logger\nvi.mock(\"../logger\", () => ({\n  logger: {\n    debug: vi.fn(),\n    info: vi.fn(),\n    warn: vi.fn(),\n    error: vi.fn(),\n  },\n}));\n\n// Mock clients\nvi.mock(\"../clients/unified-client\", () => {\n  const mockServerConfig = {\n    downloadClientWorkingFolders: \"/downloads/completed\",\n    enableCompletedDownloadHandling: true,\n    autoRedownloadFailed: true,\n    checkForFinishedDownloadInterval: 1,\n    autoRedownloadFailedFromInteractiveSearch: false,\n  };\n\n  const mockClient = {\n    getDownloadClientConfig: vi.fn(() => mockServerConfig),\n    updateDownloadClientConfig: vi.fn(() => mockServerConfig),\n  };\n  return {\n    getUnifiedClient: vi.fn(() => ({\n      api: mockClient,\n    })),\n    getSpecificClient: vi.fn(() => mockClient),\n  };\n});\n\nvi.mock(\"../clients/radarr-client\");\nvi.mock(\"../clients/sonarr-client\");\nvi.mock(\"../clients/lidarr-client\");\nvi.mock(\"../clients/readarr-client\");\nvi.mock(\"../clients/whisparr-client\");\n\n// Create a mock ServerCache\nconst createMockServerCache = (): ServerCache => {\n  return {} as unknown as ServerCache;\n};\n\ndescribe(\"downloadClientConfigSyncer\", () => {\n  beforeEach(() => {\n    vi.clearAllMocks();\n  });\n\n  afterEach(() => {\n    vi.clearAllMocks();\n  });\n\n  describe(\"syncDownloadClientConfig\", () => {\n    it(\"should export syncDownloadClientConfig function\", async () => {\n      const { syncDownloadClientConfig } = await import(\"./downloadClientConfigSyncer\");\n      expect(syncDownloadClientConfig).toBeDefined();\n      expect(typeof syncDownloadClientConfig).toBe(\"function\");\n    });\n\n    it(\"should return updated: false when no download_clients config specified\", async () => {\n      const { syncDownloadClientConfig } = await import(\"./downloadClientConfigSyncer\");\n      const config: MergedConfigInstance = {\n        custom_formats: [],\n        quality_profiles: [],\n      };\n      const serverCache = createMockServerCache();\n\n      const result = await syncDownloadClientConfig(\"RADARR\", config, serverCache);\n\n      expect(result.updated).toBe(false);\n      expect(result.arrType).toBe(\"RADARR\");\n    });\n\n    it(\"should handle all arrTypes without error\", async () => {\n      const { syncDownloadClientConfig } = await import(\"./downloadClientConfigSyncer\");\n      const arrTypes: ArrType[] = [\"RADARR\", \"SONARR\", \"LIDARR\", \"READARR\", \"WHISPARR\"];\n      const baseConfig: MergedConfigInstance = {\n        custom_formats: [],\n        quality_profiles: [],\n      };\n\n      for (const arrType of arrTypes) {\n        const config = { ...baseConfig };\n        const result = await syncDownloadClientConfig(arrType, config, createMockServerCache());\n        expect(result.arrType).toBe(arrType);\n        expect(result.updated).toBe(false);\n      }\n    });\n\n    it(\"should normalize field names from snake_case to camelCase\", async () => {\n      const { syncDownloadClientConfig } = await import(\"./downloadClientConfigSyncer\");\n      const { getSpecificClient } = await import(\"../clients/unified-client\");\n      const { getEnvs } = await import(\"../env\");\n\n      const mockGetEnvs = vi.mocked(getEnvs);\n      mockGetEnvs.mockReturnValue({\n        DRY_RUN: false,\n        LOG_LEVEL: \"silent\",\n        DEBUG_CREATE_FILES: false,\n        CONFIGARR_VERSION: \"test\",\n        ROOT_PATH: \"/tmp/test\",\n      } as any);\n\n      const mockGetConfig = vi.fn().mockResolvedValue({\n        id: 1,\n        downloadClientWorkingFolders: \"/old\",\n        enableCompletedDownloadHandling: false,\n      });\n\n      const mockUpdateConfig = vi.fn().mockResolvedValue({});\n\n      const mockClient = {\n        getDownloadClientConfig: mockGetConfig,\n        updateDownloadClientConfig: mockUpdateConfig,\n      };\n      vi.mocked(getSpecificClient).mockReturnValue(mockClient as any);\n\n      const config: MergedConfigInstance = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          config: {\n            download_client_working_folders: \"/new\",\n            enable_completed_download_handling: true,\n            auto_redownload_failed: false,\n          },\n        },\n      };\n\n      const result = await syncDownloadClientConfig(\"SONARR\", config, createMockServerCache());\n\n      expect(result.updated).toBe(true);\n      expect(mockUpdateConfig).toHaveBeenCalled();\n\n      const callArgs = mockUpdateConfig.mock.calls[0];\n      if (callArgs && callArgs[1]) {\n        const updatedConfig = callArgs[1] as Record<string, any>;\n        expect(updatedConfig).toHaveProperty(\"downloadClientWorkingFolders\", \"/new\");\n        expect(updatedConfig).toHaveProperty(\"enableCompletedDownloadHandling\", true);\n      }\n    });\n\n    it(\"should filter Radarr-only fields for Radarr\", async () => {\n      const { syncDownloadClientConfig } = await import(\"./downloadClientConfigSyncer\");\n      const { getSpecificClient } = await import(\"../clients/unified-client\");\n      const { getEnvs } = await import(\"../env\");\n\n      const mockGetEnvs = vi.mocked(getEnvs);\n      mockGetEnvs.mockReturnValue({\n        DRY_RUN: false,\n        LOG_LEVEL: \"silent\",\n        DEBUG_CREATE_FILES: false,\n        CONFIGARR_VERSION: \"test\",\n        ROOT_PATH: \"/tmp/test\",\n      } as any);\n\n      const mockGetConfig = vi.fn().mockResolvedValue({\n        id: 1,\n        checkForFinishedDownloadInterval: 1,\n      });\n\n      const mockUpdateConfig = vi.fn().mockResolvedValue({});\n\n      const mockClient = {\n        getDownloadClientConfig: mockGetConfig,\n        updateDownloadClientConfig: mockUpdateConfig,\n      };\n      vi.mocked(getSpecificClient).mockReturnValue(mockClient as any);\n\n      const config: MergedConfigInstance = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          config: {\n            check_for_finished_download_interval: 5,\n          },\n        },\n      };\n\n      await syncDownloadClientConfig(\"RADARR\", config, createMockServerCache());\n\n      expect(mockUpdateConfig).toHaveBeenCalled();\n      const callArgs = mockUpdateConfig.mock.calls[0];\n      if (callArgs && callArgs[1]) {\n        const updatedConfig = callArgs[1] as Record<string, any>;\n        expect(updatedConfig).toHaveProperty(\"checkForFinishedDownloadInterval\", 5);\n      }\n    });\n\n    it(\"should filter out Radarr-only fields for Sonarr\", async () => {\n      const { syncDownloadClientConfig } = await import(\"./downloadClientConfigSyncer\");\n      const { getSpecificClient } = await import(\"../clients/unified-client\");\n      const { getEnvs } = await import(\"../env\");\n\n      const mockGetEnvs = vi.mocked(getEnvs);\n      mockGetEnvs.mockReturnValue({\n        DRY_RUN: false,\n        LOG_LEVEL: \"silent\",\n        DEBUG_CREATE_FILES: false,\n        CONFIGARR_VERSION: \"test\",\n        ROOT_PATH: \"/tmp/test\",\n      } as any);\n\n      const mockGetConfig = vi.fn().mockResolvedValue({\n        id: 1,\n      });\n\n      const mockUpdateConfig = vi.fn().mockResolvedValue({});\n\n      const mockClient = {\n        getDownloadClientConfig: mockGetConfig,\n        updateDownloadClientConfig: mockUpdateConfig,\n      };\n      vi.mocked(getSpecificClient).mockReturnValue(mockClient as any);\n\n      const config: MergedConfigInstance = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          config: {\n            check_for_finished_download_interval: 5,\n            enable_completed_download_handling: true,\n          },\n        },\n      };\n\n      await syncDownloadClientConfig(\"SONARR\", config, createMockServerCache());\n\n      expect(mockUpdateConfig).toHaveBeenCalled();\n      const callArgs = mockUpdateConfig.mock.calls[0];\n      if (callArgs && callArgs[1]) {\n        const updatedConfig = callArgs[1] as Record<string, any>;\n        expect(updatedConfig).not.toHaveProperty(\"checkForFinishedDownloadInterval\");\n        expect(updatedConfig).toHaveProperty(\"enableCompletedDownloadHandling\", true);\n      }\n    });\n\n    it(\"should filter out autoRedownloadFailedFromInteractiveSearch for Whisparr\", async () => {\n      const { syncDownloadClientConfig } = await import(\"./downloadClientConfigSyncer\");\n      const { getSpecificClient } = await import(\"../clients/unified-client\");\n      const { getEnvs } = await import(\"../env\");\n\n      const mockGetEnvs = vi.mocked(getEnvs);\n      mockGetEnvs.mockReturnValue({\n        DRY_RUN: false,\n        LOG_LEVEL: \"silent\",\n        DEBUG_CREATE_FILES: false,\n        CONFIGARR_VERSION: \"test\",\n        ROOT_PATH: \"/tmp/test\",\n      } as any);\n\n      const mockGetConfig = vi.fn().mockResolvedValue({\n        id: 1,\n      });\n\n      const mockUpdateConfig = vi.fn().mockResolvedValue({});\n\n      const mockClient = {\n        getDownloadClientConfig: mockGetConfig,\n        updateDownloadClientConfig: mockUpdateConfig,\n      };\n      vi.mocked(getSpecificClient).mockReturnValue(mockClient as any);\n\n      const config: MergedConfigInstance = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          config: {\n            auto_redownload_failed_from_interactive_search: true,\n            enable_completed_download_handling: true,\n          },\n        },\n      };\n\n      await syncDownloadClientConfig(\"WHISPARR\", config, createMockServerCache());\n\n      expect(mockUpdateConfig).toHaveBeenCalled();\n      const callArgs = mockUpdateConfig.mock.calls[0];\n      if (callArgs && callArgs[1]) {\n        const updatedConfig = callArgs[1] as Record<string, any>;\n        expect(updatedConfig).not.toHaveProperty(\"autoRedownloadFailedFromInteractiveSearch\");\n        expect(updatedConfig).toHaveProperty(\"enableCompletedDownloadHandling\", true);\n      }\n    });\n\n    it(\"should not update if config is already up-to-date\", async () => {\n      const { syncDownloadClientConfig } = await import(\"./downloadClientConfigSyncer\");\n      const { getSpecificClient } = await import(\"../clients/unified-client\");\n      const { getEnvs } = await import(\"../env\");\n\n      const mockGetEnvs = vi.mocked(getEnvs);\n      mockGetEnvs.mockReturnValue({\n        DRY_RUN: false,\n        LOG_LEVEL: \"silent\",\n        DEBUG_CREATE_FILES: false,\n        CONFIGARR_VERSION: \"test\",\n        ROOT_PATH: \"/tmp/test\",\n      } as any);\n\n      const mockGetConfig = vi.fn().mockResolvedValue({\n        id: 1,\n        enableCompletedDownloadHandling: true,\n        autoRedownloadFailed: false,\n      });\n\n      const mockUpdateConfig = vi.fn().mockResolvedValue({});\n\n      const mockClient = {\n        getDownloadClientConfig: mockGetConfig,\n        updateDownloadClientConfig: mockUpdateConfig,\n      };\n      vi.mocked(getSpecificClient).mockReturnValue(mockClient as any);\n\n      const config: MergedConfigInstance = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          config: {\n            enable_completed_download_handling: true,\n            auto_redownload_failed: false,\n          },\n        },\n      };\n\n      const result = await syncDownloadClientConfig(\"SONARR\", config, createMockServerCache());\n\n      expect(result.updated).toBe(false);\n      expect(mockUpdateConfig).not.toHaveBeenCalled();\n    });\n\n    it(\"should handle DRY_RUN mode\", async () => {\n      const { syncDownloadClientConfig } = await import(\"./downloadClientConfigSyncer\");\n      const { getSpecificClient } = await import(\"../clients/unified-client\");\n      const { getEnvs } = await import(\"../env\");\n\n      const mockGetEnvs = vi.mocked(getEnvs);\n      mockGetEnvs.mockReturnValue({\n        DRY_RUN: true,\n        LOG_LEVEL: \"silent\",\n        DEBUG_CREATE_FILES: false,\n        CONFIGARR_VERSION: \"test\",\n        ROOT_PATH: \"/tmp/test\",\n      } as any);\n\n      const mockGetConfig = vi.fn().mockResolvedValue({\n        id: 1,\n        enableCompletedDownloadHandling: false,\n      });\n\n      const mockUpdateConfig = vi.fn().mockResolvedValue({});\n\n      const mockClient = {\n        getDownloadClientConfig: mockGetConfig,\n        updateDownloadClientConfig: mockUpdateConfig,\n      };\n      vi.mocked(getSpecificClient).mockReturnValue(mockClient as any);\n\n      const config: MergedConfigInstance = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          config: {\n            enable_completed_download_handling: true,\n          },\n        },\n      };\n\n      const result = await syncDownloadClientConfig(\"RADARR\", config, createMockServerCache());\n\n      expect(result.updated).toBe(true);\n      expect(mockUpdateConfig).not.toHaveBeenCalled();\n    });\n\n    it(\"should throw error on API call failure\", async () => {\n      const { syncDownloadClientConfig } = await import(\"./downloadClientConfigSyncer\");\n      const { getSpecificClient } = await import(\"../clients/unified-client\");\n      const { getEnvs } = await import(\"../env\");\n\n      const mockGetEnvs = vi.mocked(getEnvs);\n      mockGetEnvs.mockReturnValue({\n        DRY_RUN: false,\n        LOG_LEVEL: \"silent\",\n        DEBUG_CREATE_FILES: false,\n        CONFIGARR_VERSION: \"test\",\n        ROOT_PATH: \"/tmp/test\",\n      } as any);\n\n      const mockGetConfig = vi.fn().mockRejectedValue(new Error(\"API Error: Connection failed\"));\n\n      const mockClient = {\n        getDownloadClientConfig: mockGetConfig,\n        updateDownloadClientConfig: vi.fn(),\n      };\n      vi.mocked(getSpecificClient).mockReturnValue(mockClient as any);\n\n      const config: MergedConfigInstance = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          config: {\n            enable_completed_download_handling: true,\n          },\n        },\n      };\n\n      await expect(syncDownloadClientConfig(\"RADARR\", config, createMockServerCache())).rejects.toThrow(\n        \"Download client config sync failed for RADARR\",\n      );\n    });\n\n    it(\"should include common fields for all arrTypes\", async () => {\n      const { syncDownloadClientConfig } = await import(\"./downloadClientConfigSyncer\");\n      const { getSpecificClient } = await import(\"../clients/unified-client\");\n      const { getEnvs } = await import(\"../env\");\n\n      const mockGetEnvs = vi.mocked(getEnvs);\n      mockGetEnvs.mockReturnValue({\n        DRY_RUN: false,\n        LOG_LEVEL: \"silent\",\n        DEBUG_CREATE_FILES: false,\n        CONFIGARR_VERSION: \"test\",\n        ROOT_PATH: \"/tmp/test\",\n      } as any);\n\n      const mockGetConfig = vi.fn().mockResolvedValue({\n        id: 1,\n      });\n\n      const mockUpdateConfig = vi.fn().mockResolvedValue({});\n\n      const mockClient = {\n        getDownloadClientConfig: mockGetConfig,\n        updateDownloadClientConfig: mockUpdateConfig,\n      };\n      vi.mocked(getSpecificClient).mockReturnValue(mockClient as any);\n\n      const config: MergedConfigInstance = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          config: {\n            enable_completed_download_handling: false,\n            auto_redownload_failed: true,\n          },\n        },\n      };\n\n      const arrTypes: ArrType[] = [\"RADARR\", \"SONARR\", \"LIDARR\", \"READARR\"];\n\n      for (const arrType of arrTypes) {\n        mockUpdateConfig.mockClear();\n\n        await syncDownloadClientConfig(arrType, config, createMockServerCache());\n\n        expect(mockUpdateConfig).toHaveBeenCalled();\n        const callArgs = mockUpdateConfig.mock.calls[0];\n        if (callArgs && callArgs[1]) {\n          const updatedConfig = callArgs[1] as Record<string, any>;\n          expect(updatedConfig).toHaveProperty(\"enableCompletedDownloadHandling\", false);\n          expect(updatedConfig).toHaveProperty(\"autoRedownloadFailed\", true);\n        }\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "src/downloadClientConfig/downloadClientConfigSyncer.ts",
    "content": "import { ServerCache } from \"../cache\";\nimport { RadarrClient } from \"../clients/radarr-client\";\nimport { SonarrClient } from \"../clients/sonarr-client\";\nimport { LidarrClient } from \"../clients/lidarr-client\";\nimport { ReadarrClient } from \"../clients/readarr-client\";\nimport { WhisparrClient } from \"../clients/whisparr-client\";\nimport { getSpecificClient } from \"../clients/unified-client\";\nimport { logger } from \"../logger\";\nimport { ArrType } from \"../types/common.types\";\nimport { InputConfigDownloadClientConfig, MergedConfigInstance } from \"../types/config.types\";\nimport { getEnvs } from \"../env\";\nimport { camelToSnake, snakeToCamel } from \"../util\";\nimport { DownloadClientConfigSyncResult } from \"./downloadClientConfig.types\";\n\n/**\n * Normalize field names from snake_case (config) to camelCase (server)\n */\nfunction normalizeConfigFields(configFields: InputConfigDownloadClientConfig): Record<string, any> {\n  const normalized: Record<string, any> = {};\n\n  for (const [key, value] of Object.entries(configFields)) {\n    if (value !== undefined) {\n      const camelKey = snakeToCamel(key);\n      normalized[camelKey] = value;\n    }\n  }\n\n  return normalized;\n}\n\n/**\n * Filter config fields based on arrType support\n * Returns only fields that are supported by the specific arrType\n */\nfunction filterFieldsByArrType(fields: Record<string, any>, arrType: ArrType): Record<string, any> {\n  const filtered: Record<string, any> = {};\n\n  // Common fields for all *arr types\n  const commonFields = [\"downloadClientWorkingFolders\", \"enableCompletedDownloadHandling\", \"autoRedownloadFailed\"];\n\n  // Instance-specific fields\n  const radarrOnlyFields = [\"checkForFinishedDownloadInterval\"];\n  const nonWhisparrFields = [\"autoRedownloadFailedFromInteractiveSearch\"];\n\n  for (const [key, value] of Object.entries(fields)) {\n    // Include common fields\n    if (commonFields.includes(key)) {\n      filtered[key] = value;\n      continue;\n    }\n\n    // Radarr-only field\n    if (radarrOnlyFields.includes(key)) {\n      if (arrType === \"RADARR\") {\n        filtered[key] = value;\n      }\n      continue;\n    }\n\n    // Non-Whisparr field (Sonarr, Lidarr, Readarr have this)\n    if (nonWhisparrFields.includes(key)) {\n      if (arrType !== \"WHISPARR\") {\n        filtered[key] = value;\n      }\n      continue;\n    }\n  }\n\n  return filtered;\n}\n\n/**\n * Check if server config differs from desired config\n */\nfunction configHasChanges(serverConfig: Record<string, any>, desiredConfig: Record<string, any>): boolean {\n  for (const [key, value] of Object.entries(desiredConfig)) {\n    if (serverConfig[key] !== value) {\n      return true;\n    }\n  }\n  return false;\n}\n\n/**\n * Sync download client configuration for a specific *arr instance\n */\nexport async function syncDownloadClientConfig(\n  arrType: ArrType,\n  config: MergedConfigInstance,\n  serverCache: ServerCache,\n): Promise<DownloadClientConfigSyncResult> {\n  const downloadClientConfig = config.download_clients?.config;\n\n  if (!downloadClientConfig) {\n    logger.debug(`No download client config specified for ${arrType}`);\n    return { updated: false, arrType };\n  }\n\n  try {\n    // Get specific client for this arrType - TypeScript infers the correct type\n    const client = getSpecificClient(arrType);\n\n    // Fetch current server config\n    logger.debug(`Fetching download client config from ${arrType}...`);\n    const serverConfig = await client.getDownloadClientConfig();\n\n    // Normalize and filter desired config\n    const normalizedConfig = normalizeConfigFields(downloadClientConfig);\n    const desiredConfig = filterFieldsByArrType(normalizedConfig, arrType);\n\n    logger.debug(`Server config: ${JSON.stringify(serverConfig)}`);\n    logger.debug(`Desired config: ${JSON.stringify(desiredConfig)}`);\n\n    // Check if changes are needed\n    if (!configHasChanges(serverConfig, desiredConfig)) {\n      logger.info(`Download client config for ${arrType} is already up-to-date`);\n      return { updated: false, arrType };\n    }\n\n    logger.info(`Download client config changes detected for ${arrType}`);\n\n    if (getEnvs().DRY_RUN) {\n      logger.info(\"DryRun: Would update download client config.\");\n      return { updated: true, arrType };\n    }\n\n    // Merge with server config to preserve unmanaged fields\n    const mergedConfig = { ...serverConfig, ...desiredConfig };\n\n    // Update the config\n    const configId = serverConfig.id?.toString() || \"1\";\n    logger.info(`Updating download client config for ${arrType}...`);\n    await client.updateDownloadClientConfig(configId, mergedConfig);\n\n    logger.info(`Successfully updated download client config for ${arrType}`);\n    return { updated: true, arrType };\n  } catch (error: unknown) {\n    const errorMessage = error instanceof Error ? error.message : String(error);\n    logger.error(`Failed to sync download client config for ${arrType}: ${errorMessage}`);\n    throw new Error(`Download client config sync failed for ${arrType}: ${errorMessage}`);\n  }\n}\n"
  },
  {
    "path": "src/downloadClients/downloadClientBase.test.ts",
    "content": "import { describe, expect, test, vi, beforeEach } from \"vitest\";\nimport { BaseDownloadClientSync } from \"./downloadClientBase\";\nimport type { InputConfigDownloadClient } from \"../types/config.types\";\nimport type { ServerCache } from \"../cache\";\nimport type { IArrClient } from \"../clients/unified-client\";\nimport type { TagResource } from \"../__generated__/radarr/data-contracts\";\nimport { DownloadProtocol } from \"../__generated__/radarr/data-contracts\";\nimport { ArrType } from \"../types/common.types\";\nimport { DownloadClientResource } from \"../types/download-client.types\";\n\nclass MockDownloadClientSync extends BaseDownloadClientSync {\n  constructor() {\n    super();\n  }\n\n  public testValidateDownloadClient(config: InputConfigDownloadClient, schema: DownloadClientResource[]) {\n    return this.validateDownloadClient(config, schema);\n  }\n\n  public testResolveTagNamesToIds(tagNames: (string | number)[], serverTags: TagResource[]) {\n    return this.resolveTagNamesToIds(tagNames, serverTags);\n  }\n\n  public testNormalizeConfigFields(configFields: Record<string, any>, arrType: ArrType) {\n    return this.normalizeConfigFields(configFields, arrType);\n  }\n\n  public testGetApi(): IArrClient {\n    return this.getApi();\n  }\n\n  protected getArrType(): ArrType {\n    return \"RADARR\";\n  }\n\n  protected async calculateDiff(\n    configClients: InputConfigDownloadClient[],\n    serverClients: DownloadClientResource[],\n    cache: ServerCache,\n    updatePassword?: boolean,\n  ) {\n    return {\n      create: [],\n      update: [],\n      unchanged: [],\n      deleted: [],\n    };\n  }\n\n  public async resolveConfig(\n    config: InputConfigDownloadClient,\n    cache: ServerCache,\n    serverClient?: DownloadClientResource,\n    partialUpdate?: boolean,\n  ) {\n    return {} as DownloadClientResource;\n  }\n\n  protected createClient() {\n    throw new Error(\"Not implemented in test\");\n  }\n\n  protected updateClient() {\n    throw new Error(\"Not implemented in test\");\n  }\n\n  protected deleteClient() {\n    throw new Error(\"Not implemented in test\");\n  }\n}\n\ndescribe(\"BaseDownloadClientSync – utility methods\", () => {\n  let sync: MockDownloadClientSync;\n\n  beforeEach(() => {\n    sync = new MockDownloadClientSync();\n  });\n\n  describe(\"field normalization\", () => {\n    test(\"normalizes snake_case fields to camelCase\", () => {\n      const result = sync.testNormalizeConfigFields(\n        {\n          use_ssl: true,\n          api_key: \"test123\",\n          recent_priority: 5,\n          normal_field: \"value\",\n        },\n        \"RADARR\",\n      );\n\n      expect(result).toHaveProperty(\"useSsl\", true);\n      expect(result).toHaveProperty(\"apiKey\", \"test123\");\n      expect(result).toHaveProperty(\"recentPriority\", 5);\n      expect(result).toHaveProperty(\"normalField\", \"value\");\n\n      // Should keep original fields for backward compatibility\n      expect(result).toHaveProperty(\"use_ssl\", true);\n      expect(result).toHaveProperty(\"api_key\", \"test123\");\n      expect(result).toHaveProperty(\"recent_priority\", 5);\n      expect(result).toHaveProperty(\"normal_field\", \"value\");\n    });\n\n    test(\"handles empty config\", () => {\n      const result = sync.testNormalizeConfigFields({}, \"RADARR\");\n      expect(result).toEqual({});\n    });\n\n    test(\"handles nested objects\", () => {\n      const result = sync.testNormalizeConfigFields(\n        {\n          nested_obj: {\n            inner_field: \"value\",\n          },\n        },\n        \"RADARR\",\n      );\n\n      expect(result).toHaveProperty(\"nestedObj\");\n      expect(result.nestedObj).toEqual({ inner_field: \"value\" });\n      expect(result).toHaveProperty(\"nested_obj\");\n    });\n  });\n\n  describe(\"tag resolution\", () => {\n    test(\"resolves tag names to IDs (case-insensitive)\", () => {\n      const serverTags: TagResource[] = [\n        { id: 1, label: \"movies\" },\n        { id: 2, label: \"4K\" },\n        { id: 3, label: \"Test-Tag\" },\n      ];\n\n      const { ids, missingTags } = sync.testResolveTagNamesToIds([\"Movies\", \"4k\", \"test-tag\"], serverTags);\n\n      expect(ids).toEqual([1, 2, 3]);\n      expect(missingTags).toEqual([]);\n    });\n\n    test(\"handles numeric tag IDs\", () => {\n      const serverTags: TagResource[] = [\n        { id: 1, label: \"movies\" },\n        { id: 2, label: \"4K\" },\n      ];\n\n      const { ids, missingTags } = sync.testResolveTagNamesToIds([1, 2, 999], serverTags);\n\n      expect(ids).toEqual([1, 2, 999]);\n      expect(missingTags).toEqual([]);\n    });\n\n    test(\"identifies missing tags\", () => {\n      const serverTags: TagResource[] = [{ id: 1, label: \"movies\" }];\n\n      const { ids, missingTags } = sync.testResolveTagNamesToIds([\"movies\", \"missing1\", \"missing2\"], serverTags);\n\n      expect(ids).toEqual([1]);\n      expect(missingTags).toEqual([\"missing1\", \"missing2\"]);\n    });\n\n    test(\"handles empty tag list\", () => {\n      const { ids, missingTags } = sync.testResolveTagNamesToIds([], []);\n\n      expect(ids).toEqual([]);\n      expect(missingTags).toEqual([]);\n    });\n  });\n\n  describe(\"validation\", () => {\n    test(\"validates valid configuration\", () => {\n      const mockSchema: DownloadClientResource[] = [\n        {\n          id: 0,\n          name: \"TestClient\",\n          implementation: \"TestImplementation\",\n          protocol: DownloadProtocol.Torrent,\n          enable: true,\n          priority: 1,\n          fields: [],\n          tags: [],\n          removeCompletedDownloads: true,\n          removeFailedDownloads: true,\n          configContract: \"\",\n        },\n      ];\n\n      const config: InputConfigDownloadClient = {\n        name: \"Valid Client\",\n        type: \"TestImplementation\",\n        priority: 5,\n      };\n\n      const result = sync.testValidateDownloadClient(config, mockSchema);\n\n      expect(result.valid).toBe(true);\n      expect(result.errors).toEqual([]);\n    });\n\n    test(\"rejects configuration with missing name\", () => {\n      const mockSchema: DownloadClientResource[] = [\n        {\n          id: 0,\n          name: \"TestClient\",\n          implementation: \"TestImplementation\",\n          protocol: DownloadProtocol.Torrent,\n          enable: true,\n          priority: 1,\n          fields: [],\n          tags: [],\n          removeCompletedDownloads: true,\n          removeFailedDownloads: true,\n          configContract: \"\",\n        },\n      ];\n\n      const config = {\n        type: \"TestImplementation\",\n      } as any;\n\n      const result = sync.testValidateDownloadClient(config, mockSchema);\n\n      expect(result.valid).toBe(false);\n      expect(result.errors.length).toBeGreaterThan(0);\n    });\n\n    test(\"rejects configuration with missing type\", () => {\n      const mockSchema: DownloadClientResource[] = [\n        {\n          id: 0,\n          name: \"TestClient\",\n          implementation: \"TestImplementation\",\n          protocol: DownloadProtocol.Torrent,\n          enable: true,\n          priority: 1,\n          fields: [],\n          tags: [],\n          removeCompletedDownloads: true,\n          removeFailedDownloads: true,\n          configContract: \"\",\n        },\n      ];\n\n      const config = {\n        name: \"Test\",\n      } as any;\n\n      const result = sync.testValidateDownloadClient(config, mockSchema);\n\n      expect(result.valid).toBe(false);\n      expect(result.errors.length).toBeGreaterThan(0);\n    });\n  });\n\n  describe(\"lazy API initialization\", () => {\n    test(\"getApi() throws error when not configured\", () => {\n      expect(() => sync.testGetApi()).toThrow(\"Please configure API first.\");\n    });\n  });\n});\n"
  },
  {
    "path": "src/downloadClients/downloadClientBase.ts",
    "content": "import { z } from \"zod\";\nimport { ServerCache } from \"../cache\";\nimport { getUnifiedClient, IArrClient } from \"../clients/unified-client\";\nimport { getEnvs } from \"../env\";\nimport { logger } from \"../logger\";\nimport { ArrType } from \"../types/common.types\";\nimport { InputConfigDownloadClient, MergedConfigInstance } from \"../types/config.types\";\nimport {\n  DownloadClientDiff,\n  DownloadClientField,\n  DownloadClientResource,\n  DownloadClientSyncResult,\n  TagLike,\n  ValidationResult,\n} from \"../types/download-client.types\";\nimport { camelToSnake, snakeToCamel } from \"../util\";\n\n// Constants\nconst PRIORITY_MIN = 1;\nconst PRIORITY_MAX = 50;\nconst NAME_MAX_LENGTH = 100;\n\nconst DownloadClientConfigSchema = z.object({\n  name: z\n    .string()\n    .min(1, \"Download client name is required\")\n    .max(NAME_MAX_LENGTH, `Download client name must be ${NAME_MAX_LENGTH} characters or less`),\n  type: z.string().min(1, \"Download client type is required\"),\n  enable: z.boolean().optional().default(true),\n  priority: z.number().int(\"Priority must be an integer\").positive(\"Priority must be positive\").optional().default(1),\n  remove_completed_downloads: z.boolean().optional().default(true),\n  remove_failed_downloads: z.boolean().optional().default(true),\n  fields: z.record(z.string(), z.unknown()).optional(),\n  tags: z\n    .array(z.union([z.string().min(1, \"Tag name cannot be empty\"), z.number().int().positive(\"Tag ID must be a positive integer\")]))\n    .optional()\n    .default([]),\n});\n\nexport abstract class BaseDownloadClientSync {\n  private _api: IArrClient | undefined;\n  protected readonly logger = logger;\n\n  protected getApi(): IArrClient {\n    if (!this._api) {\n      this._api = getUnifiedClient();\n    }\n    return this._api;\n  }\n\n  protected abstract getArrType(): ArrType;\n\n  protected abstract calculateDiff(\n    configClients: InputConfigDownloadClient[],\n    serverClients: DownloadClientResource[],\n    cache: ServerCache,\n    updatePassword?: boolean,\n  ): Promise<DownloadClientDiff>;\n\n  public abstract resolveConfig(\n    config: InputConfigDownloadClient,\n    cache: ServerCache,\n    serverClient?: DownloadClientResource,\n    partialUpdate?: boolean,\n  ): Promise<DownloadClientResource>;\n\n  public normalizeConfigFields(configFields: Record<string, unknown>, arrType: ArrType): Record<string, unknown> {\n    const normalized: Record<string, unknown> = {};\n\n    for (const [key, value] of Object.entries(configFields)) {\n      // Convert to camelCase\n      const camelKey = snakeToCamel(key);\n      normalized[camelKey] = value;\n\n      // Keep original key if different (for backward compatibility with snake_case)\n      if (key !== camelKey) {\n        normalized[key] = value;\n      }\n    }\n\n    return normalized;\n  }\n\n  public resolveTagNamesToIds(tagNames: (string | number)[], serverTags: TagLike[]): { ids: number[]; missingTags: string[] } {\n    const ids: number[] = [];\n    const missingTags: string[] = [];\n\n    for (const tag of tagNames) {\n      if (typeof tag === \"number\") {\n        ids.push(tag);\n      } else {\n        const serverTag = serverTags.find((t) => t.label?.toLowerCase() === tag.toLowerCase());\n        if (serverTag?.id) {\n          ids.push(serverTag.id);\n        } else {\n          missingTags.push(tag);\n        }\n      }\n    }\n\n    return { ids, missingTags };\n  }\n\n  protected findImplementationInSchema(schema: DownloadClientResource[], implementation: string): DownloadClientResource | undefined {\n    return schema.find((s) => s.implementation?.toLowerCase() === implementation.toLowerCase());\n  }\n\n  protected mergeFieldsWithSchema(\n    schemaFields: DownloadClientField[],\n    configFields: Record<string, unknown>,\n    arrType: ArrType,\n    serverFields: DownloadClientField[] | null | undefined,\n    partialUpdate = false,\n  ): DownloadClientField[] {\n    const normalizedFields = this.normalizeConfigFields(configFields, arrType);\n    const baseFields = partialUpdate && serverFields ? serverFields : schemaFields;\n\n    return baseFields.map((field) => {\n      const fieldName = field.name ?? \"\";\n      const configValue = normalizedFields[fieldName];\n      return configValue !== undefined ? { ...field, value: configValue } : field;\n    });\n  }\n\n  protected validateDownloadClientConfig(config: unknown): {\n    valid: boolean;\n    errors: string[];\n    warnings: string[];\n    data?: z.infer<typeof DownloadClientConfigSchema>;\n  } {\n    const result = DownloadClientConfigSchema.safeParse(config);\n\n    return result.success\n      ? { valid: true, errors: [], warnings: [], data: result.data }\n      : {\n          valid: false,\n          errors: result.error.issues.map((e) => `${e.path.join(\".\")}: ${e.message}`),\n          warnings: [],\n        };\n  }\n\n  public validateDownloadClient(config: InputConfigDownloadClient, schema: DownloadClientResource[]): ValidationResult {\n    const zodValidation = this.validateDownloadClientConfig(config);\n\n    if (!zodValidation.valid) {\n      return zodValidation;\n    }\n\n    const errors: string[] = [];\n    const warnings: string[] = [];\n\n    // Validate type exists in server schema\n    const template = this.findImplementationInSchema(schema, config.type);\n    if (!template) {\n      const availableTypes = schema\n        .map((s) => s.implementation)\n        .filter(Boolean)\n        .join(\", \");\n      errors.push(`Unknown download client type '${config.type}'. Available types: ${availableTypes}`);\n    } else {\n      // Validate potentially required fields\n      const requiredFields = (template.fields ?? []).filter((f) => f.value === undefined || f.value === null || f.value === \"\");\n\n      // Normalize config fields to check against schema field names\n      const arrType = this.getArrType();\n      const normalizedFields = this.normalizeConfigFields(config.fields || {}, arrType);\n\n      for (const field of requiredFields) {\n        const fieldName = field.name;\n        // Check if the field exists in either the original config or normalized fields\n        const fieldExists = fieldName && ((config.fields && fieldName in config.fields) || fieldName in normalizedFields);\n\n        if (fieldName && !fieldExists) {\n          warnings.push(`Field '${camelToSnake(fieldName)}' may be required for ${config.type}`);\n        }\n      }\n    }\n\n    // Validate priority range\n    if (config.priority !== undefined && (config.priority < PRIORITY_MIN || config.priority > PRIORITY_MAX)) {\n      warnings.push(`Priority ${config.priority} is outside typical range (${PRIORITY_MIN}-${PRIORITY_MAX})`);\n    }\n\n    return { valid: errors.length === 0, errors, warnings };\n  }\n\n  protected async getDownloadClientSchema(cache: ServerCache): Promise<DownloadClientResource[]> {\n    const cached = cache.getDownloadClientSchema();\n    if (cached) {\n      return cached;\n    }\n\n    const schema = await this.getApi().getDownloadClientSchema();\n    cache.setDownloadClientSchema(schema);\n    return schema;\n  }\n\n  public filterUnmanagedClients(\n    serverClients: DownloadClientResource[],\n    configClients: InputConfigDownloadClient[],\n    deleteConfig: Exclude<MergedConfigInstance[\"download_clients\"], undefined>[\"delete_unmanaged\"],\n  ): DownloadClientResource[] {\n    const { enabled = false, ignore = [] } = deleteConfig ?? {};\n\n    if (!enabled) {\n      return [];\n    }\n\n    // Create composite keys for managed clients (name + implementation)\n    const configKeys = new Set(configClients.filter((c) => c.name && c.type).map((c) => `${c.name}::${c.type.toLowerCase()}`));\n\n    return serverClients.filter((server) => {\n      const name = server.name ?? \"\";\n      const implementation = server.implementation?.toLowerCase() ?? \"\";\n      const key = `${name}::${implementation}`;\n\n      return !configKeys.has(key) && !ignore.includes(name);\n    });\n  }\n\n  private async validateConfigClients(\n    configClients: InputConfigDownloadClient[],\n    schema: DownloadClientResource[],\n  ): Promise<{\n    validClients: InputConfigDownloadClient[];\n    hasErrors: boolean;\n  }> {\n    // Check for duplicate name + type combinations\n    const compositeKeyCounts = new Map<string, number>();\n    for (const config of configClients) {\n      if (config.name && config.type) {\n        const key = `${config.name}::${config.type.toLowerCase()}`;\n        const current = compositeKeyCounts.get(key) ?? 0;\n        compositeKeyCounts.set(key, current + 1);\n      }\n    }\n\n    const validClients: InputConfigDownloadClient[] = [];\n    let hasErrors = false;\n\n    for (const config of configClients) {\n      const validation = this.validateDownloadClient(config, schema);\n      const compositeKey = config.name && config.type ? `${config.name}::${config.type.toLowerCase()}` : \"\";\n      const duplicateCount = compositeKey ? (compositeKeyCounts.get(compositeKey) ?? 0) : 0;\n      const isDuplicateComposite = !!compositeKey && duplicateCount > 1;\n\n      if (!validation.valid) {\n        this.logger.error(`Validation failed for download client '${config.name}': ${validation.errors.join(\", \")}`);\n        hasErrors = true;\n      }\n\n      if (isDuplicateComposite) {\n        this.logger.error(\n          `Validation failed for download client '${config.name}' (${config.type}): name and type combination must be unique (appears ${duplicateCount} times in configuration)`,\n        );\n        hasErrors = true;\n      }\n\n      if (validation.warnings.length > 0) {\n        this.logger.warn(`Validation warnings for download client '${config.name}': ${validation.warnings.join(\", \")}`);\n      }\n\n      if (validation.valid && !isDuplicateComposite) {\n        validClients.push(config);\n      }\n    }\n\n    if (hasErrors) {\n      this.logger.warn(\n        \"One or more download client configurations are invalid and will be skipped. Valid clients will still be processed.\",\n      );\n    }\n\n    return { validClients, hasErrors };\n  }\n\n  private async createMissingTags(configClients: InputConfigDownloadClient[], serverCache: ServerCache): Promise<void> {\n    const allMissingTags = new Set<string>();\n\n    for (const config of configClients) {\n      if (config.tags) {\n        const { missingTags } = this.resolveTagNamesToIds(config.tags, serverCache.tags);\n        missingTags.forEach((tag) => allMissingTags.add(tag));\n      }\n    }\n\n    if (allMissingTags.size === 0) {\n      return;\n    }\n\n    this.logger.info(`Creating missing tags for download clients: ${Array.from(allMissingTags).join(\", \")}`);\n\n    for (const tagName of allMissingTags) {\n      try {\n        const newTag = await this.getApi().createTag({ label: tagName });\n        serverCache.tags.push(newTag);\n        this.logger.debug(`Created tag: '${tagName}' (ID: ${newTag.id})`);\n      } catch (error: unknown) {\n        const errorMessage = error instanceof Error ? error.message : String(error);\n        this.logger.error(`Failed to create tag '${tagName}': ${errorMessage}`);\n        throw new Error(\"Tag creation failed. Cannot proceed with download client sync.\");\n      }\n    }\n  }\n\n  private async createClients(configs: InputConfigDownloadClient[], serverCache: ServerCache): Promise<number> {\n    if (configs.length === 0) {\n      return 0;\n    }\n\n    let added = 0;\n\n    for (const config of configs) {\n      try {\n        this.logger.info(`Creating download client: '${config.name}' (${config.type})...`);\n\n        const payload = await this.resolveConfig(config, serverCache);\n\n        await this.getApi().createDownloadClient(payload);\n        added++;\n        this.logger.info(`Created download client: '${config.name}' (${config.type})`);\n      } catch (error: unknown) {\n        const errorMessage = error instanceof Error ? error.message : String(error);\n        this.logger.error(`Create download client '${config.name}' failed: ${errorMessage}`);\n        const httpError = error as any;\n        if (httpError.response?.data) {\n          this.logger.debug(`Server response: ${JSON.stringify(httpError.response.data)}`);\n        }\n      }\n    }\n\n    return added;\n  }\n\n  private async updateClients(updates: DownloadClientDiff[\"update\"], serverCache: ServerCache): Promise<number> {\n    if (updates.length === 0) {\n      return 0;\n    }\n\n    let updated = 0;\n\n    for (const { config, server, partialUpdate } of updates) {\n      try {\n        const updateType = partialUpdate ? \"partial\" : \"full\";\n        this.logger.info(`Updating download client: '${config.name}' (${updateType} update)...`);\n\n        const payload = await this.resolveConfig(config, serverCache, server, partialUpdate);\n        payload.id = server.id; // Preserve server ID\n\n        await this.getApi().updateDownloadClient(server.id!.toString(), payload);\n        updated++;\n        this.logger.info(`Updated download client: '${config.name}' (${config.type})`);\n      } catch (error: unknown) {\n        const errorMessage = error instanceof Error ? error.message : String(error);\n        this.logger.error(`Update download client '${config.name}' failed: ${errorMessage}`);\n        const httpError = error as any;\n        if (httpError.response?.data) {\n          this.logger.debug(`Server response: ${JSON.stringify(httpError.response.data)}`);\n        }\n      }\n    }\n\n    return updated;\n  }\n\n  private async deleteUnmanagedClients(unmanagedClients: DownloadClientResource[]): Promise<number> {\n    if (unmanagedClients.length === 0) {\n      return 0;\n    }\n\n    let removed = 0;\n\n    for (const client of unmanagedClients) {\n      try {\n        this.logger.info(`Deleting unmanaged download client: '${client.name ?? \"Unknown\"}' (${client.implementation})...`);\n        await this.getApi().deleteDownloadClient(client.id!.toString());\n        removed++;\n        this.logger.info(`Deleted unmanaged download client: '${client.name ?? \"Unknown\"}'`);\n      } catch (error: unknown) {\n        const errorMessage = error instanceof Error ? error.message : String(error);\n        this.logger.error(`Delete download client '${client.name ?? \"Unknown\"}' failed: ${errorMessage}`);\n\n        // Provide additional diagnostic information for common issues\n        if (errorMessage.includes(\"in use\")) {\n          this.logger.warn(`Download client '${client.name ?? \"Unknown\"}' may be in use by active downloads`);\n        }\n      }\n    }\n\n    return removed;\n  }\n\n  public async syncDownloadClients(config: MergedConfigInstance, serverCache: ServerCache): Promise<DownloadClientSyncResult> {\n    const configClients = config.download_clients?.data ?? [];\n    const updatePassword = config.download_clients?.update_password ?? false;\n\n    if (configClients.length === 0 && !config.download_clients?.delete_unmanaged?.enabled) {\n      this.logger.info(\"No download clients configured and delete_unmanaged not enabled, skipping\");\n      return { added: 0, updated: 0, removed: 0 };\n    }\n\n    // Get schema and server clients\n    this.logger.debug(\"Fetching download client schema...\");\n    const schema = await this.getDownloadClientSchema(serverCache);\n\n    this.logger.debug(\"Fetching existing download clients...\");\n    const serverClients = await this.getApi().getDownloadClients();\n\n    this.logger.info(`Found ${serverClients.length} download client(s) on server`);\n\n    // Validate configurations\n    this.logger.debug(\"Validating download client configurations...\");\n    const { validClients } = await this.validateConfigClients(configClients, schema);\n\n    // Create missing tags\n    await this.createMissingTags(validClients, serverCache);\n\n    // Calculate diff\n    const diff = await this.calculateDiff(validClients, serverClients, serverCache, updatePassword);\n\n    this.logger.info(\n      `Download clients diff - Create: ${diff.create.length}, Update: ${diff.update.length}, Unchanged: ${diff.unchanged.length}`,\n    );\n\n    if (getEnvs().DRY_RUN) {\n      this.logger.info(\"DryRun: Would update download clients.\");\n      return {\n        added: diff.create.length,\n        updated: diff.update.length,\n        removed: diff.deleted.length,\n      };\n    }\n\n    // Execute changes\n    const [added, updated] = await Promise.all([\n      this.createClients(diff.create, serverCache),\n      this.updateClients(diff.update, serverCache),\n    ]);\n\n    const removed = config.download_clients?.delete_unmanaged?.enabled\n      ? await this.deleteUnmanagedClients(\n          this.filterUnmanagedClients(serverClients, configClients, config.download_clients.delete_unmanaged),\n        )\n      : 0;\n\n    if (added > 0 || updated > 0 || removed > 0) {\n      this.logger.info(`Download client synchronization complete: +${added} ~${updated} -${removed}`);\n    } else {\n      this.logger.info(\"Download client synchronization complete - no changes needed\");\n    }\n\n    return { added, updated, removed };\n  }\n}\n"
  },
  {
    "path": "src/downloadClients/downloadClientGeneric.test.ts",
    "content": "import { beforeEach, describe, expect, test, vi } from \"vitest\";\nimport { DownloadProtocol } from \"../__generated__/radarr/data-contracts\";\nimport { ServerCache } from \"../cache\";\nimport { ArrType } from \"../types/common.types\";\nimport type { InputConfigDownloadClient } from \"../types/config.types\";\nimport { GenericDownloadClientSync } from \"./downloadClientGeneric\";\nimport { DownloadClientResource } from \"../types/download-client.types\";\n\nvi.mock(\"../clients/unified-client\", () => ({\n  getUnifiedClient: vi.fn(() => ({\n    getDownloadClients: vi.fn(),\n    getDownloadClientSchema: vi.fn(),\n    createDownloadClient: vi.fn(),\n    updateDownloadClient: vi.fn(),\n    deleteDownloadClient: vi.fn(),\n    testDownloadClient: vi.fn(),\n  })),\n}));\n\ndescribe(\"GenericDownloadClientSync – ARR type handling\", () => {\n  let sync: GenericDownloadClientSync;\n\n  beforeEach(() => {\n    vi.clearAllMocks();\n  });\n\n  describe(\"constructor and ARR type initialization\", () => {\n    test(\"creates instance for RADARR\", () => {\n      sync = new GenericDownloadClientSync(\"RADARR\");\n      expect(sync).toBeInstanceOf(GenericDownloadClientSync);\n    });\n\n    test(\"creates instance for SONARR\", () => {\n      sync = new GenericDownloadClientSync(\"SONARR\");\n      expect(sync).toBeInstanceOf(GenericDownloadClientSync);\n    });\n\n    test(\"creates instance for LIDARR\", () => {\n      sync = new GenericDownloadClientSync(\"LIDARR\");\n      expect(sync).toBeInstanceOf(GenericDownloadClientSync);\n    });\n\n    test(\"creates instance for READARR\", () => {\n      sync = new GenericDownloadClientSync(\"READARR\");\n      expect(sync).toBeInstanceOf(GenericDownloadClientSync);\n    });\n\n    test(\"creates instance for WHISPARR\", () => {\n      sync = new GenericDownloadClientSync(\"WHISPARR\");\n      expect(sync).toBeInstanceOf(GenericDownloadClientSync);\n    });\n  });\n\n  describe(\"ARR type-specific behavior\", () => {\n    test(\"normalizes fields consistently across different ARR types\", () => {\n      const testCases: [ArrType][] = [[\"SONARR\"], [\"LIDARR\"], [\"RADARR\"], [\"WHISPARR\"], [\"READARR\"]];\n\n      testCases.forEach(([arrType]) => {\n        sync = new GenericDownloadClientSync(arrType);\n        // Test snake_case to camelCase normalization (no category handling)\n        const result = sync.normalizeConfigFields(\n          {\n            use_ssl: true,\n            api_key: \"test\",\n            movie_imported_category: \"test\",\n          },\n          arrType,\n        );\n\n        expect(result).toHaveProperty(\"useSsl\", true);\n        expect(result).toHaveProperty(\"apiKey\", \"test\");\n        expect(result).toHaveProperty(\"movieImportedCategory\", \"test\");\n        // Backward compatibility\n        expect(result).toHaveProperty(\"use_ssl\", true);\n        expect(result).toHaveProperty(\"api_key\", \"test\");\n        expect(result).toHaveProperty(\"movie_imported_category\", \"test\");\n      });\n    });\n  });\n\n  describe(\"download client comparison logic\", () => {\n    const makeCache = () => new ServerCache([], [], [], []);\n\n    test(\"compares clients correctly with omission semantics\", () => {\n      sync = new GenericDownloadClientSync(\"RADARR\");\n      const cache = makeCache();\n\n      const serverClient: DownloadClientResource = {\n        id: 1,\n        name: \"Test Client\",\n        implementation: \"qBittorrent\",\n        protocol: DownloadProtocol.Torrent,\n        enable: true,\n        priority: 1,\n        fields: [{ name: \"host\", value: \"localhost\" }],\n        tags: [],\n        removeCompletedDownloads: true,\n        removeFailedDownloads: true,\n        configContract: \"\",\n      };\n\n      const configClient: InputConfigDownloadClient = {\n        name: \"Test Client\",\n        type: \"qBittorrent\",\n        fields: { host: \"localhost\" },\n      };\n\n      const isEqual = sync.isDownloadClientEqual(configClient, serverClient, cache);\n      expect(isEqual).toBe(true);\n    });\n\n    test(\"detects differences in specified fields\", () => {\n      sync = new GenericDownloadClientSync(\"RADARR\");\n      const cache = makeCache();\n\n      const serverClient: DownloadClientResource = {\n        id: 1,\n        name: \"Test Client\",\n        implementation: \"qBittorrent\",\n        protocol: DownloadProtocol.Torrent,\n        enable: true,\n        priority: 1,\n        fields: [{ name: \"host\", value: \"localhost\" }],\n        tags: [],\n        removeCompletedDownloads: true,\n        removeFailedDownloads: true,\n        configContract: \"\",\n      };\n\n      const configClient: InputConfigDownloadClient = {\n        name: \"Test Client\",\n        type: \"qBittorrent\",\n        enable: false, // explicitly different\n        fields: { host: \"localhost\" },\n      };\n\n      const isEqual = sync.isDownloadClientEqual(configClient, serverClient, cache);\n      expect(isEqual).toBe(false);\n    });\n\n    test(\"handles exact field name matches\", () => {\n      sync = new GenericDownloadClientSync(\"SONARR\");\n      const cache = makeCache();\n\n      const serverClient: DownloadClientResource = {\n        id: 1,\n        name: \"Test Client\",\n        implementation: \"qBittorrent\",\n        protocol: DownloadProtocol.Torrent,\n        enable: true,\n        priority: 1,\n        fields: [\n          { name: \"host\", value: \"localhost\" },\n          { name: \"port\", value: \"8080\" },\n        ],\n        tags: [],\n        removeCompletedDownloads: true,\n        removeFailedDownloads: true,\n        configContract: \"\",\n      };\n\n      const configClient: InputConfigDownloadClient = {\n        name: \"Test Client\",\n        type: \"qBittorrent\",\n        fields: {\n          host: \"localhost\",\n          port: \"8080\",\n        },\n      };\n\n      const isEqual = sync.isDownloadClientEqual(configClient, serverClient, cache);\n      expect(isEqual).toBe(true);\n    });\n\n    test(\"handles password and apiKey masking without false diff\", () => {\n      sync = new GenericDownloadClientSync(\"RADARR\");\n      const cache = new ServerCache([], [], [], []);\n\n      // Server with masked password\n      const serverClient: DownloadClientResource = {\n        id: 2,\n        name: \"qBit 4K\",\n        enable: false,\n        protocol: DownloadProtocol.Torrent,\n        priority: 1,\n        removeCompletedDownloads: true,\n        removeFailedDownloads: true,\n        implementation: \"qBittorrent\",\n        fields: [\n          { name: \"host\", value: \"qbittorrent\" },\n          { name: \"port\", value: 8080 },\n          { name: \"password\", value: \"********\" }, // Masked password from server\n          { name: \"apiKey\", value: \"********\" }, // Masked api_key from server\n        ],\n        tags: [],\n      };\n\n      // Config with actual password\n      const configClient: InputConfigDownloadClient = {\n        name: \"qBit 4K\",\n        type: \"qbittorrent\",\n        enable: false,\n        priority: 1,\n        fields: {\n          host: \"qbittorrent\",\n          port: 8080,\n          password: \"changeme_p\", // Actual password in config\n          api_key: \"changeme_k\", // Actual api_key in config\n        },\n      };\n\n      const isEqual = sync.isDownloadClientEqual(configClient, serverClient, cache);\n      expect(isEqual).toBe(true); // Should NOT detect changes due to password masking\n    });\n\n    test(\"uses exact field names without false diff\", () => {\n      sync = new GenericDownloadClientSync(\"RADARR\");\n      const cache = new ServerCache([], [], [], []);\n      cache.tags = [\n        { id: 2, label: \"4K\" },\n        { id: 3, label: \"Anime\" },\n      ];\n\n      // EXACT server data from your JSON\n      const serverClient: DownloadClientResource = {\n        enable: false,\n        protocol: DownloadProtocol.Torrent,\n        priority: 1,\n        removeCompletedDownloads: true,\n        removeFailedDownloads: true,\n        name: \"qBit 4K\",\n        fields: [\n          { name: \"host\", value: \"qbittorrent\" },\n          { name: \"port\", value: 8080 },\n          { name: \"useSsl\", value: false },\n          { name: \"urlBase\", value: \"/\" },\n          { name: \"username\", value: \"sonarr\" },\n          { name: \"password\", value: \"changeme\" },\n          { name: \"movieCategory\", value: \"radarr\" },\n          { name: \"movieImportedCategory\", value: \"series-4k\" },\n          { name: \"recentMoviePriority\", value: 0 },\n          { name: \"olderMoviePriority\", value: 0 },\n          { name: \"initialState\", value: 0 },\n          { name: \"sequentialOrder\", value: false },\n          { name: \"firstAndLast\", value: false },\n          { name: \"contentLayout\", value: 0 },\n        ],\n        implementationName: \"qBittorrent\",\n        implementation: \"QBittorrent\",\n        configContract: \"QBittorrentSettings\",\n        infoLink: \"https://wiki.servarr.com/radarr/supported#qbittorrent\",\n        tags: [2, 3],\n        id: 2,\n      };\n\n      // Config should use the exact field name from server schema\n      const configClient: InputConfigDownloadClient = {\n        name: \"qBit 4K\",\n        type: \"qbittorrent\",\n        enable: false,\n        priority: 1,\n        remove_completed_downloads: true,\n        remove_failed_downloads: true,\n        tags: [\"4K\", \"Anime\"],\n        fields: {\n          host: \"qbittorrent\",\n          port: 8080,\n          use_ssl: false,\n          url_base: \"/\",\n          username: \"sonarr\",\n          password: \"changeme\",\n          movieImportedCategory: \"series-4k\", // Use exact field name required by qBittorrent in Radarr\n        },\n      };\n\n      const isEqual = sync.isDownloadClientEqual(configClient, serverClient, cache);\n\n      // Now it should be true since we have the right field mapping\n      expect(isEqual).toBe(true); // Should NOT detect changes anymore\n    });\n\n    test(\"update_password forces password comparison\", () => {\n      sync = new GenericDownloadClientSync(\"RADARR\");\n      const cache = new ServerCache([], [], [], []);\n\n      // Server with masked password\n      const serverClient: DownloadClientResource = {\n        id: 2,\n        name: \"qBit 4K\",\n        enable: false,\n        protocol: DownloadProtocol.Torrent,\n        priority: 1,\n        implementation: \"qBittorrent\",\n        fields: [\n          { name: \"host\", value: \"qbittorrent\" },\n          { name: \"port\", value: 8080 },\n          { name: \"password\", value: \"********\" }, // Masked password from server\n        ],\n        tags: [],\n      };\n\n      // Config with different password\n      const configClient: InputConfigDownloadClient = {\n        name: \"qBit 4K\",\n        type: \"qbittorrent\",\n        enable: false,\n        priority: 1,\n        fields: {\n          host: \"qbittorrent\",\n          port: 8080,\n          password: \"different-password\", // Different password in config\n        },\n      };\n\n      // Without update_password, should be equal (password masked)\n      const isEqualWithoutUpdate = sync.isDownloadClientEqual(configClient, serverClient, cache, false);\n      expect(isEqualWithoutUpdate).toBe(true);\n\n      // With update_password, should NOT be equal (different passwords)\n      const isEqualWithUpdate = sync.isDownloadClientEqual(configClient, serverClient, cache, true);\n      expect(isEqualWithUpdate).toBe(false);\n    });\n  });\n\n  describe(\"partial update logic\", () => {\n    test(\"correctly identifies when to use partial updates\", () => {\n      sync = new GenericDownloadClientSync(\"RADARR\");\n\n      // Config with no properties should not use partial update\n      const createConfig: InputConfigDownloadClient = {\n        name: \"New Client\",\n        type: \"qBittorrent\",\n      };\n      expect(sync.shouldUsePartialUpdate(createConfig)).toBe(false);\n\n      // Config with field overrides should not use partial update (full update)\n      const fieldConfig: InputConfigDownloadClient = {\n        name: \"Field Client\",\n        type: \"qBittorrent\",\n        fields: { host: \"localhost\" },\n      };\n      expect(sync.shouldUsePartialUpdate(fieldConfig)).toBe(false);\n\n      // Config with single top-level property should use partial update\n      const partialConfig: InputConfigDownloadClient = {\n        name: \"Partial Client\",\n        type: \"qBittorrent\",\n        enable: false,\n      };\n      expect(sync.shouldUsePartialUpdate(partialConfig)).toBe(true);\n\n      // Config with too many top-level properties should not use partial update\n      const fullConfig: InputConfigDownloadClient = {\n        name: \"Full Client\",\n        type: \"qBittorrent\",\n        enable: false,\n        priority: 1,\n        remove_completed_downloads: true,\n        remove_failed_downloads: false,\n        tags: [\"test\"],\n      };\n      expect(sync.shouldUsePartialUpdate(fullConfig)).toBe(false);\n    });\n  });\n\n  describe(\"client filtering logic\", () => {\n    test(\"correctly identifies unmanaged clients\", () => {\n      sync = new GenericDownloadClientSync(\"RADARR\");\n\n      const serverClients: DownloadClientResource[] = [\n        {\n          id: 1,\n          name: \"Managed Client\",\n          implementation: \"qBittorrent\",\n          protocol: DownloadProtocol.Torrent,\n          enable: true,\n          priority: 1,\n          fields: [],\n          tags: [],\n          removeCompletedDownloads: true,\n          removeFailedDownloads: true,\n          configContract: \"\",\n        },\n        {\n          id: 2,\n          name: \"Unmanaged Client\",\n          implementation: \"Transmission\",\n          protocol: DownloadProtocol.Torrent,\n          enable: true,\n          priority: 2,\n          fields: [],\n          tags: [],\n          removeCompletedDownloads: true,\n          removeFailedDownloads: true,\n          configContract: \"\",\n        },\n      ];\n\n      const configClients: InputConfigDownloadClient[] = [\n        {\n          name: \"Managed Client\",\n          type: \"qBittorrent\",\n        },\n      ];\n\n      const deleteConfig = { enabled: true, ignore: [] };\n\n      const unmanagedClients = sync.filterUnmanagedClients(serverClients, configClients, deleteConfig);\n\n      expect(unmanagedClients).toHaveLength(1);\n      expect(unmanagedClients[0]?.name).toBe(\"Unmanaged Client\");\n      expect(unmanagedClients[0]?.implementation).toBe(\"Transmission\");\n    });\n\n    test(\"respects delete unmanaged disabled\", () => {\n      sync = new GenericDownloadClientSync(\"RADARR\");\n\n      const serverClients: DownloadClientResource[] = [\n        {\n          id: 1,\n          name: \"Client\",\n          implementation: \"qBittorrent\",\n          protocol: DownloadProtocol.Torrent,\n          enable: true,\n          priority: 1,\n          fields: [],\n          tags: [],\n          removeCompletedDownloads: true,\n          removeFailedDownloads: true,\n          configContract: \"\",\n        },\n      ];\n\n      const configClients: InputConfigDownloadClient[] = [];\n\n      const unmanagedClients = sync.filterUnmanagedClients(serverClients, configClients, { enabled: false });\n\n      expect(unmanagedClients).toEqual([]);\n    });\n\n    test(\"respects ignore list\", () => {\n      sync = new GenericDownloadClientSync(\"RADARR\");\n\n      const serverClients: DownloadClientResource[] = [\n        {\n          id: 1,\n          name: \"Ignored Client\",\n          implementation: \"qBittorrent\",\n          protocol: DownloadProtocol.Torrent,\n          enable: true,\n          priority: 1,\n          fields: [],\n          tags: [],\n          removeCompletedDownloads: true,\n          removeFailedDownloads: true,\n          configContract: \"\",\n        },\n      ];\n\n      const configClients: InputConfigDownloadClient[] = [];\n\n      const deleteConfig = { enabled: true, ignore: [\"Ignored Client\"] };\n\n      const unmanagedClients = sync.filterUnmanagedClients(serverClients, configClients, deleteConfig);\n\n      expect(unmanagedClients).toEqual([]);\n    });\n  });\n});\n"
  },
  {
    "path": "src/downloadClients/downloadClientGeneric.ts",
    "content": "import { ServerCache } from \"../cache\";\nimport { logger } from \"../logger\";\nimport { ArrType } from \"../types/common.types\";\nimport { InputConfigDownloadClient } from \"../types/config.types\";\nimport { DownloadClientDiff, DownloadClientField, DownloadClientResource } from \"../types/download-client.types\";\nimport { snakeToCamel } from \"../util\";\nimport { BaseDownloadClientSync } from \"./downloadClientBase\";\n\nexport class GenericDownloadClientSync extends BaseDownloadClientSync {\n  constructor(private arrType: ArrType) {\n    super();\n  }\n\n  protected getArrType(): ArrType {\n    return this.arrType;\n  }\n\n  public isDownloadClientEqual = (\n    config: InputConfigDownloadClient,\n    server: DownloadClientResource,\n    cache: ServerCache,\n    updatePassword: boolean = false,\n  ): boolean => {\n    // Basic comparison\n    if (config.name !== server.name) return false;\n\n    // Compare only when specified\n    // Omitted means \"do not manage\"\n    if (config.enable !== undefined && config.enable !== server.enable) return false;\n    if (config.priority !== undefined && config.priority !== server.priority) return false;\n    if (config.remove_completed_downloads !== undefined && config.remove_completed_downloads !== server.removeCompletedDownloads) {\n      return false;\n    }\n    if (config.remove_failed_downloads !== undefined && config.remove_failed_downloads !== server.removeFailedDownloads) {\n      return false;\n    }\n\n    // Compare implementation\n    if (config.type.toLowerCase() !== server.implementation?.toLowerCase()) return false;\n\n    // Compare fields (normalize to support snake_case)\n    const normalizedConfigFields = this.normalizeConfigFields(config.fields || {}, this.arrType);\n    const serverFields = server.fields || [];\n\n    for (const serverField of serverFields) {\n      const fieldName = serverField.name;\n      if (!fieldName) continue;\n\n      const configValue = normalizedConfigFields[fieldName];\n      const serverValue = serverField.value;\n\n      // Skip if config doesn't specify this field (use server default)\n      if (configValue === undefined) continue;\n\n      // Deep comparison for arrays and objects\n      let valuesMatch = JSON.stringify(configValue) === JSON.stringify(serverValue);\n\n      // Special handling for password fields - server masks them as \"********\" unless update_password is enabled\n      if (\n        !valuesMatch &&\n        (fieldName.toLowerCase().includes(\"password\") || fieldName.toLowerCase().includes(\"apikey\")) &&\n        serverValue === \"********\" &&\n        typeof configValue === \"string\" &&\n        configValue.length > 0 &&\n        !updatePassword\n      ) {\n        valuesMatch = true;\n      }\n\n      if (!valuesMatch) {\n        return false;\n      }\n    }\n\n    const serverFieldNames = new Set(\n      serverFields.map((f: DownloadClientField) => f.name).filter((name): name is string => typeof name === \"string\" && name.length > 0),\n    );\n\n    for (const key of Object.keys(normalizedConfigFields)) {\n      // Only check camelCase field names against server (skip snake_case duplicates)\n      if (key !== snakeToCamel(key)) {\n        // This is a snake_case field, skip the server field check\n        continue;\n      }\n\n      if (!serverFieldNames.has(key) && normalizedConfigFields[key] !== undefined) {\n        // Config references a field that does not exist on the server; treat as different\n        this.logger.warn(`Config field '${key}' does not exist on server`);\n        return false;\n      }\n    }\n\n    // Compare tags (resolve names to IDs first)\n    const configTags = config.tags ?? [];\n    const { ids: resolvedTagIds, missingTags } = this.resolveTagNamesToIds(configTags, cache.tags);\n    const serverTags = server.tags ?? [];\n\n    const sortedConfigTagIds = [...resolvedTagIds].sort();\n    const sortedServerTags = [...serverTags].sort();\n\n    if (JSON.stringify(sortedConfigTagIds) !== JSON.stringify(sortedServerTags)) {\n      return false;\n    }\n\n    return true;\n  };\n\n  public shouldUsePartialUpdate = (config: InputConfigDownloadClient): boolean => {\n    const hasFieldOverrides = !!(config.fields && Object.keys(config.fields).length > 0);\n\n    // When field overrides are present, default to full updates so that the config is authoritative\n    if (hasFieldOverrides) {\n      return false;\n    }\n\n    const hasTags = Array.isArray(config.tags) && config.tags.length > 0;\n\n    const specifiedTopLevelProps = [\n      config.enable !== undefined,\n      config.priority !== undefined,\n      config.remove_completed_downloads !== undefined,\n      config.remove_failed_downloads !== undefined,\n      hasTags,\n    ].filter(Boolean).length;\n\n    // Only treat it as a partial update when the user is toggling a small subset of top-level properties\n    return specifiedTopLevelProps > 0 && specifiedTopLevelProps <= 2;\n  };\n\n  async calculateDiff(\n    configClients: InputConfigDownloadClient[],\n    serverClients: DownloadClientResource[],\n    cache: ServerCache,\n    updatePassword: boolean = false,\n  ): Promise<DownloadClientDiff> {\n    const create: InputConfigDownloadClient[] = [];\n    const update: { config: InputConfigDownloadClient; server: DownloadClientResource; partialUpdate: boolean }[] = [];\n    const unchanged: { config: InputConfigDownloadClient; server: DownloadClientResource }[] = [];\n\n    for (const config of configClients) {\n      // Use composite key (name + type) to match clients\n      const serverClient = serverClients.find(\n        (s: DownloadClientResource) => s.name === config.name && s.implementation?.toLowerCase() === config.type.toLowerCase(),\n      );\n\n      if (!serverClient) {\n        create.push(config);\n      } else if (!this.isDownloadClientEqual(config, serverClient, cache, updatePassword)) {\n        const partialUpdate = this.shouldUsePartialUpdate(config);\n        update.push({ config, server: serverClient, partialUpdate });\n      } else {\n        unchanged.push({ config, server: serverClient });\n      }\n    }\n\n    // Find clients to delete (on server but not in config) using composite keys\n    const configKeys = new Set(configClients.map((c: InputConfigDownloadClient) => `${c.name}::${c.type.toLowerCase()}`));\n    const deleted = serverClients.filter(\n      (s: DownloadClientResource) => !configKeys.has(`${s.name ?? \"\"}::${s.implementation?.toLowerCase() ?? \"\"}`),\n    );\n\n    return { create, update, unchanged, deleted };\n  }\n\n  async resolveConfig(\n    config: InputConfigDownloadClient,\n    cache: ServerCache,\n    serverClient?: DownloadClientResource,\n    partialUpdate: boolean = false,\n  ): Promise<DownloadClientResource> {\n    const schema = await this.getDownloadClientSchema(cache);\n    const template = this.findImplementationInSchema(schema, config.type);\n\n    if (!template) {\n      throw new Error(`Download client implementation '${config.type}' not found in schema`);\n    }\n\n    // Resolve tag names to IDs (all tags should exist by this point due to batch creation in syncDownloadClients)\n    let tagIds: number[] = [];\n    if (config.tags && config.tags.length > 0) {\n      const { ids, missingTags } = this.resolveTagNamesToIds(config.tags, cache.tags);\n\n      if (missingTags.length > 0) {\n        // This should not happen as tags are created in batch before this function is called\n        logger.warn(\n          `Missing tags for download client '${config.name}': ${missingTags.join(\", \")}. ` +\n            `These should have been created during batch tag creation.`,\n        );\n      }\n\n      tagIds = ids;\n    }\n\n    const mergedFields = this.mergeFieldsWithSchema(\n      template.fields || [],\n      config.fields || {},\n      this.arrType,\n      serverClient?.fields ?? undefined,\n      partialUpdate,\n    );\n\n    return {\n      enable: config.enable ?? serverClient?.enable ?? true,\n      protocol: template.protocol,\n      priority: config.priority ?? serverClient?.priority ?? 1,\n      removeCompletedDownloads: config.remove_completed_downloads ?? serverClient?.removeCompletedDownloads ?? true,\n      removeFailedDownloads: config.remove_failed_downloads ?? serverClient?.removeFailedDownloads ?? true,\n      name: config.name,\n      fields: mergedFields,\n      implementationName: template.implementationName,\n      implementation: template.implementation,\n      configContract: template.configContract,\n      infoLink: template.infoLink,\n      tags: tagIds,\n    };\n  }\n}\n"
  },
  {
    "path": "src/downloadClients/downloadClientSyncer.test.ts",
    "content": "import { describe, expect, test } from \"vitest\";\nimport type { MergedTagResource } from \"../types/merged.types\";\nimport type { DownloadClientResource, TagResource } from \"../__generated__/radarr/data-contracts\";\nimport { DownloadProtocol } from \"../__generated__/radarr/data-contracts\";\nimport { ServerCache } from \"../cache\";\nimport type { InputConfigDownloadClient } from \"../types/config.types\";\nimport { GenericDownloadClientSync } from \"./downloadClientGeneric\";\n\ntype TestDownloadClientResource = Omit<DownloadClientResource, \"protocol\"> & {\n  protocol?: DownloadProtocol;\n};\n\n// Helper function for tests\nconst getTestSync = () => new GenericDownloadClientSync(\"RADARR\");\n\ndescribe(\"downloadClientSyncer – tag resolution\", () => {\n  test(\"resolves tag names to IDs (case-insensitive)\", () => {\n    const serverTags: TagResource[] = [\n      { id: 1, label: \"movies\" },\n      { id: 2, label: \"4K\" },\n      { id: 3, label: \"Test-Tag\" },\n    ];\n\n    const { ids, missingTags } = getTestSync().resolveTagNamesToIds([\"Movies\", \"4k\", \"test-tag\"], serverTags);\n\n    expect(ids).toEqual([1, 2, 3]);\n    expect(missingTags).toEqual([]);\n  });\n\n  test(\"resolves numeric tag IDs directly\", () => {\n    const serverTags: TagResource[] = [\n      { id: 1, label: \"movies\" },\n      { id: 2, label: \"4K\" },\n    ];\n\n    const { ids, missingTags } = getTestSync().resolveTagNamesToIds([1, 2, 999], serverTags);\n\n    expect(ids).toEqual([1, 2, 999]);\n    expect(missingTags).toEqual([]);\n  });\n\n  test(\"identifies missing tags\", () => {\n    const serverTags: TagResource[] = [{ id: 1, label: \"movies\" }];\n\n    const { ids, missingTags } = getTestSync().resolveTagNamesToIds([\"movies\", \"missing1\", \"missing2\"], serverTags);\n\n    expect(ids).toEqual([1]);\n    expect(missingTags).toEqual([\"missing1\", \"missing2\"]);\n  });\n\n  test(\"handles mixed tag names and IDs\", () => {\n    const serverTags: TagResource[] = [\n      { id: 1, label: \"movies\" },\n      { id: 2, label: \"4K\" },\n    ];\n\n    const { ids, missingTags } = getTestSync().resolveTagNamesToIds([\"movies\", 2, \"new-tag\", 999], serverTags);\n\n    expect(ids).toEqual([1, 2, 999]);\n    expect(missingTags).toEqual([\"new-tag\"]);\n  });\n\n  test(\"handles empty tag list\", () => {\n    const serverTags: TagResource[] = [];\n\n    const { ids, missingTags } = getTestSync().resolveTagNamesToIds([], serverTags);\n\n    expect(ids).toEqual([]);\n    expect(missingTags).toEqual([]);\n  });\n});\n\ndescribe(\"downloadClientSyncer – field normalization\", () => {\n  test(\"converts snake_case to camelCase\", () => {\n    const result = getTestSync().normalizeConfigFields({ use_ssl: true, api_key: \"test123\", recent_priority: 5 }, \"RADARR\");\n\n    expect(result).toHaveProperty(\"useSsl\", true);\n    expect(result).toHaveProperty(\"apiKey\", \"test123\");\n    expect(result).toHaveProperty(\"recentPriority\", 5);\n\n    // Backward compatibility with snake_case\n    expect(result).toHaveProperty(\"use_ssl\", true);\n    expect(result).toHaveProperty(\"api_key\", \"test123\");\n    expect(result).toHaveProperty(\"recent_priority\", 5);\n  });\n\n  test(\"handles mixed field naming\", () => {\n    const result = getTestSync().normalizeConfigFields(\n      {\n        movie_imported_category: \"movies-4k\",\n        use_ssl: true,\n        host: \"localhost\",\n      },\n      \"RADARR\",\n    );\n\n    expect(result).toHaveProperty(\"movieImportedCategory\", \"movies-4k\");\n    expect(result).toHaveProperty(\"useSsl\", true);\n    expect(result).toHaveProperty(\"host\", \"localhost\");\n    expect(result).toHaveProperty(\"movie_imported_category\", \"movies-4k\"); // backward compatibility\n  });\n\n  test(\"preserves exact field names when already camelCase\", () => {\n    const result = getTestSync().normalizeConfigFields(\n      {\n        movieImportedCategory: \"4k-movies\",\n        urlBase: \"/\",\n        host: \"localhost\",\n      },\n      \"RADARR\",\n    );\n\n    expect(result).toHaveProperty(\"movieImportedCategory\", \"4k-movies\");\n    expect(result).toHaveProperty(\"urlBase\", \"/\");\n    expect(result).toHaveProperty(\"host\", \"localhost\");\n    // No duplicate fields since they're already camelCase\n    expect(result).not.toHaveProperty(\"movie_imported_category\");\n    expect(result).not.toHaveProperty(\"url_base\");\n  });\n\n  test(\"handles empty config\", () => {\n    const result = getTestSync().normalizeConfigFields({}, \"RADARR\");\n    expect(result).toEqual({});\n  });\n\n  test(\"handles nested objects\", () => {\n    const result = getTestSync().normalizeConfigFields(\n      {\n        nested_obj: {\n          inner_field: \"value\",\n        },\n      },\n      \"RADARR\",\n    );\n\n    expect(result).toHaveProperty(\"nestedObj\", {\n      inner_field: \"value\",\n    });\n  });\n});\n\ndescribe(\"downloadClientSyncer – validation\", () => {\n  const mockSchema: TestDownloadClientResource[] = [\n    {\n      id: 0,\n      name: \"qBittorrent\",\n      implementation: \"qBittorrent\",\n      protocol: DownloadProtocol.Torrent,\n      enable: true,\n      priority: 1,\n      fields: [\n        { name: \"host\", value: \"\" },\n        { name: \"port\", value: \"\" },\n      ],\n      tags: [],\n      removeCompletedDownloads: true,\n      removeFailedDownloads: true,\n      configContract: \"\",\n    },\n  ];\n\n  test(\"validates valid configuration\", () => {\n    const config: InputConfigDownloadClient = {\n      name: \"Test Client\",\n      type: \"qBittorrent\",\n      priority: 5,\n      fields: { host: \"localhost\", port: 8080 },\n    };\n\n    const result = getTestSync().validateDownloadClient(config, mockSchema as any as DownloadClientResource[]);\n\n    expect(result.valid).toBe(true);\n    expect(result.errors).toEqual([]);\n  });\n\n  test(\"rejects missing name\", () => {\n    const config: any = {\n      type: \"qBittorrent\",\n    };\n\n    const result = getTestSync().validateDownloadClient(config, mockSchema as any as DownloadClientResource[]);\n\n    expect(result.valid).toBe(false);\n    expect(result.errors.some((e) => (e.includes(\"name\") && e.includes(\"required\")) || e.includes(\"undefined\"))).toBe(true);\n  });\n\n  test(\"rejects empty name\", () => {\n    const config: any = {\n      name: \"\",\n      type: \"qBittorrent\",\n    };\n\n    const result = getTestSync().validateDownloadClient(config, mockSchema as any as DownloadClientResource[]);\n\n    expect(result.valid).toBe(false);\n    expect(result.errors.some((e) => e.includes(\"name\"))).toBe(true);\n  });\n\n  test(\"rejects missing type\", () => {\n    const config: any = {\n      name: \"Test\",\n    };\n\n    const result = getTestSync().validateDownloadClient(config, mockSchema as any as DownloadClientResource[]);\n\n    expect(result.valid).toBe(false);\n    expect(result.errors.some((e) => (e.includes(\"type\") && e.includes(\"required\")) || e.includes(\"undefined\"))).toBe(true);\n  });\n\n  test(\"rejects unknown download client type\", () => {\n    const config: InputConfigDownloadClient = {\n      name: \"Test\",\n      type: \"UnknownClient\",\n    };\n\n    const result = getTestSync().validateDownloadClient(config, mockSchema as any as DownloadClientResource[]);\n\n    expect(result.valid).toBe(false);\n    expect(result.errors.some((e) => e.includes(\"Unknown download client type\"))).toBe(true);\n  });\n\n  test(\"warns about priority outside typical range\", () => {\n    const config: InputConfigDownloadClient = {\n      name: \"Test\",\n      type: \"qBittorrent\",\n      priority: 999,\n    };\n\n    const result = getTestSync().validateDownloadClient(config, mockSchema as any as DownloadClientResource[]);\n\n    expect(result.valid).toBe(true);\n    expect(result.warnings.some((w) => w.includes(\"Priority\"))).toBe(true);\n  });\n\n  test(\"rejects invalid priority (negative)\", () => {\n    const config: any = {\n      name: \"Test\",\n      type: \"qBittorrent\",\n      priority: -1,\n    };\n\n    const result = getTestSync().validateDownloadClient(config, mockSchema as any as DownloadClientResource[]);\n\n    expect(result.valid).toBe(false);\n    expect(result.errors.some((e) => e.includes(\"Priority\"))).toBe(true);\n  });\n\n  test(\"rejects invalid tag (empty string)\", () => {\n    const config: any = {\n      name: \"Test\",\n      type: \"qBittorrent\",\n      tags: [\"valid\", \"\"],\n    };\n\n    const result = getTestSync().validateDownloadClient(config, mockSchema as any as DownloadClientResource[]);\n\n    expect(result.valid).toBe(false);\n    expect(result.errors.some((e) => e.includes(\"Tag\"))).toBe(true);\n  });\n\n  test(\"accepts valid tags (strings and numbers)\", () => {\n    const config: InputConfigDownloadClient = {\n      name: \"Test\",\n      type: \"qBittorrent\",\n      tags: [\"movies\", \"4k\", 123],\n    };\n\n    const result = getTestSync().validateDownloadClient(config, mockSchema as any as DownloadClientResource[]);\n\n    expect(result.valid).toBe(true);\n  });\n});\n\ndescribe(\"downloadClientSyncer – deletion logic\", () => {\n  test(\"filterUnmanagedClients uses composite key of name + implementation\", () => {\n    const serverClients: TestDownloadClientResource[] = [\n      {\n        id: 1,\n        enable: true,\n        protocol: DownloadProtocol.Torrent,\n        name: \"qb-download\",\n        implementation: \"qBittorrent\",\n        priority: 1,\n        tags: [],\n        removeCompletedDownloads: true,\n        removeFailedDownloads: true,\n        configContract: \"\",\n        fields: [],\n      },\n      {\n        id: 2,\n        enable: true,\n        protocol: DownloadProtocol.Torrent,\n        name: \"qb-download\",\n        implementation: \"SomeOtherImplementation\",\n        priority: 1,\n        tags: [],\n        removeCompletedDownloads: true,\n        removeFailedDownloads: true,\n        configContract: \"\",\n        fields: [],\n      },\n    ];\n\n    const configClients: InputConfigDownloadClient[] = [\n      {\n        name: \"qb-download\",\n        type: \"qBittorrent\",\n      },\n    ];\n\n    // Convert to new structure\n    const newConfig = {\n      download_clients: {\n        data: configClients,\n        update_password: false,\n      },\n    };\n\n    const deleteConfig = { enabled: true, ignore: [] };\n\n    const result = getTestSync().filterUnmanagedClients(serverClients, configClients, deleteConfig);\n\n    // Only the server client whose (name, implementation) pair does NOT\n    // appear in the configuration should be considered unmanaged.\n    expect(result.map((c) => c.id)).toEqual([2]);\n  });\n\n  test(\"filterUnmanagedClients respects delete_unmanaged=false\", () => {\n    const serverClients: TestDownloadClientResource[] = [\n      {\n        id: 1,\n        enable: true,\n        protocol: DownloadProtocol.Torrent,\n        name: \"qb-download\",\n        implementation: \"qBittorrent\",\n        priority: 1,\n        tags: [],\n        removeCompletedDownloads: true,\n        removeFailedDownloads: true,\n        configContract: \"\",\n        fields: [],\n      },\n    ];\n\n    const configClients: InputConfigDownloadClient[] = [];\n\n    const result = getTestSync().filterUnmanagedClients(serverClients, configClients, { enabled: false });\n\n    expect(result).toEqual([]);\n  });\n});\n\ndescribe(\"downloadClientSyncer – equality & omission semantics\", () => {\n  const makeCache = (tags: MergedTagResource[] = []) => new ServerCache([], [], [], []);\n\n  test(\"isDownloadClientEqual treats omitted top-level fields as 'do not manage'\", () => {\n    const cache = makeCache();\n\n    const server: TestDownloadClientResource = {\n      id: 1,\n      enable: false,\n      protocol: DownloadProtocol.Torrent,\n      name: \"client-1\",\n      implementation: \"qBittorrent\",\n      priority: 5,\n      tags: [],\n      removeCompletedDownloads: false,\n      removeFailedDownloads: false,\n      configContract: \"\",\n      fields: [],\n    };\n\n    const config: InputConfigDownloadClient = {\n      name: \"client-1\",\n      type: \"qBittorrent\",\n      // All top-level booleans and priority omitted\n      // This should be treated as \"do not manage\" for those properties\n    };\n\n    const equal = getTestSync().isDownloadClientEqual(config, server, cache);\n\n    expect(equal).toBe(true);\n  });\n\n  test(\"isDownloadClientEqual detects explicit differences when fields are set\", () => {\n    const cache = makeCache();\n\n    const server: TestDownloadClientResource = {\n      id: 1,\n      enable: true,\n      protocol: DownloadProtocol.Torrent,\n      name: \"client-1\",\n      implementation: \"qBittorrent\",\n      priority: 1,\n      tags: [],\n      removeCompletedDownloads: true,\n      removeFailedDownloads: true,\n      configContract: \"\",\n      fields: [],\n    };\n\n    const config: InputConfigDownloadClient = {\n      name: \"client-1\",\n      type: \"qBittorrent\",\n      enable: false, // explicitly different from server\n    };\n\n    const equal = getTestSync().isDownloadClientEqual(config, server, cache);\n\n    expect(equal).toBe(false);\n  });\n});\n"
  },
  {
    "path": "src/downloadClients/downloadClientSyncer.ts",
    "content": "import { ServerCache } from \"../cache\";\nimport { ArrType } from \"../types/common.types\";\nimport { MergedConfigInstance } from \"../types/config.types\";\nimport { BaseDownloadClientSync } from \"./downloadClientBase\";\nimport { GenericDownloadClientSync } from \"./downloadClientGeneric\";\nimport { DownloadClientSyncResult } from \"../types/download-client.types\";\n\nfunction createDownloadClientSync(arrType: ArrType): BaseDownloadClientSync {\n  return new GenericDownloadClientSync(arrType);\n}\n\nexport async function syncDownloadClients(\n  arrType: ArrType,\n  config: MergedConfigInstance,\n  serverCache: ServerCache,\n): Promise<DownloadClientSyncResult> {\n  const sync = createDownloadClientSync(arrType);\n  return sync.syncDownloadClients(config, serverCache);\n}\n"
  },
  {
    "path": "src/env.ts",
    "content": "import path from \"node:path\";\nimport { z } from \"zod\";\n\nconst DEFAULT_ROOT_PATH = path.resolve(process.cwd());\n\n// Build-time constants (defined by Bun at compile time)\n// These will be replaced at build time with actual values\nconst CONFIGARR_VERSION = process.env.CONFIGARR_VERSION ?? \"dev\"; // Will be replaced by --define\nconst BUILD_TIME = process.env.BUILD_TIME ?? \"\"; // Will be replaced by --define\nconst GITHUB_RUN_ID = process.env.GITHUB_RUN_ID ?? \"\"; // Will be replaced by --define\nconst GITHUB_REPO = process.env.GITHUB_REPO ?? \"\"; // Will be replaced by --define\nconst GITHUB_SHA = process.env.GITHUB_SHA ?? \"\"; // Will be replaced by --define\n\nconst schema = z.object({\n  // NODE_ENV: z.enum([\"production\", \"development\", \"test\"] as const),\n  CONFIGARR_VERSION: z.string().optional().default(CONFIGARR_VERSION),\n  LOG_LEVEL: z\n    .enum([\"trace\", \"debug\", \"info\", \"warn\", \"error\", \"fatal\"] as const)\n    .optional()\n    .default(\"info\"),\n  CONFIG_LOCATION: z.string().optional(),\n  SECRETS_LOCATION: z.string().optional(),\n  // TODO: deprecate?\n  CUSTOM_REPO_ROOT: z.string().optional(),\n  ROOT_PATH: z.string().optional().default(DEFAULT_ROOT_PATH),\n  DRY_RUN: z\n    .string()\n    .toLowerCase()\n    .transform((x) => x === \"true\")\n    .pipe(z.boolean())\n    .default(false),\n  LOAD_LOCAL_SAMPLES: z\n    .string()\n    .toLowerCase()\n    .transform((x) => x === \"true\")\n    .pipe(z.boolean())\n    .default(false),\n  DEBUG_CREATE_FILES: z\n    .string()\n    .toLowerCase()\n    .transform((x) => x === \"true\")\n    .pipe(z.boolean())\n    .default(false),\n  STOP_ON_ERROR: z\n    .string()\n    .toLowerCase()\n    .transform((x) => x === \"true\")\n    .pipe(z.boolean())\n    .default(false),\n  LOG_STACKTRACE: z\n    .string()\n    .toLowerCase()\n    .transform((x) => x === \"true\")\n    .pipe(z.boolean())\n    .default(false),\n  CONFIGARR_ENABLE_MERGE: z\n    .string()\n    .toLowerCase()\n    .transform((x) => x === \"true\")\n    .pipe(z.boolean())\n    .default(false),\n  TELEMETRY_ENABLED: z\n    .string()\n    .toLowerCase()\n    .transform((x) => x === \"true\")\n    .pipe(z.boolean())\n    .optional(),\n});\n\n// declare global {\n//   // eslint-disable-next-line @typescript-eslint/no-namespace\n//   namespace NodeJS {\n//     // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n//     interface ProcessEnv extends z.infer<typeof schema> {}\n//   }\n// }\n\nlet envs: z.infer<typeof schema>;\n\nexport function initEnvs() {\n  const parsed = schema.safeParse(process.env);\n\n  if (parsed.success === false) {\n    console.error(\"Invalid environment variables:\", parsed.error.flatten().fieldErrors);\n    throw new Error(\"Invalid environment variables.\");\n  }\n\n  envs = parsed.data;\n}\n\nexport const getEnvs = () => {\n  if (envs) return envs;\n\n  envs = schema.parse(process.env);\n\n  return envs;\n};\n\nexport const getHelpers = () => ({\n  configLocation: getEnvs().CONFIG_LOCATION ?? `${getEnvs().ROOT_PATH}/config/config.yml`,\n  secretLocation: getEnvs().SECRETS_LOCATION ?? `${getEnvs().ROOT_PATH}/config/secrets.yml`,\n  // TODO: check for different env name\n  repoPath: getEnvs().CUSTOM_REPO_ROOT ?? `${getEnvs().ROOT_PATH}/repos`,\n  /** Enable YAML << merge keys when parsing config (CONFIGARR_ENABLE_MERGE=true). */\n  enableMerge: getEnvs().CONFIGARR_ENABLE_MERGE,\n  // TODO: add stuff like isDryRun,...?\n});\n\nexport const getBuildInfo = () => ({\n  version: getEnvs().CONFIGARR_VERSION,\n  buildTime: BUILD_TIME,\n  githubRunId: GITHUB_RUN_ID,\n  githubRepo: GITHUB_REPO,\n  githubSha: GITHUB_SHA,\n});\n"
  },
  {
    "path": "src/index.ts",
    "content": "// those must be run first!\nimport \"dotenv/config\";\nimport { getBuildInfo, getEnvs, initEnvs } from \"./env\";\ninitEnvs();\n\nimport fs from \"node:fs\";\nimport { MergedCustomFormatResource, MergedQualityProfileResource } from \"./types/merged.types\";\nimport { ServerCache } from \"./cache\";\nimport { configureApi, getUnifiedClient, unsetApi } from \"./clients/unified-client\";\nimport { getConfig, mergeConfigsAndTemplates } from \"./config\";\nimport { calculateCFsToManage, deleteCustomFormat, loadCustomFormatDefinitions, loadServerCustomFormats, manageCf } from \"./custom-formats\";\nimport { calculateDelayProfilesDiff, deleteAdditionalDelayProfiles, mapToServerDelayProfile } from \"./delay-profiles\";\nimport { syncDownloadClients } from \"./downloadClients/downloadClientSyncer\";\nimport { syncDownloadClientConfig } from \"./downloadClientConfig/downloadClientConfigSyncer\";\nimport { syncRemotePaths } from \"./remotePaths/remotePathSyncer\";\nimport { syncUiConfig } from \"./uiConfigs/uiConfigSyncer\";\nimport { logger, logHeading, logInstanceHeading } from \"./logger\";\nimport { calculateMediamanagementDiff, calculateNamingDiff } from \"./media-management\";\nimport { calculateQualityDefinitionDiff, loadQualityDefinitionFromServer } from \"./quality-definitions\";\nimport {\n  calculateQualityProfilesDiff,\n  checkForConflictingCFs,\n  deleteQualityProfile,\n  getUnmanagedQualityProfiles,\n  loadQualityProfilesFromServer,\n} from \"./quality-profiles\";\nimport { syncMetadataProfiles } from \"./metadataProfiles/metadataProfileSyncer\";\nimport { cloneRecyclarrTemplateRepo } from \"./recyclarr-importer\";\nimport { loadServerTags } from \"./tags\";\nimport { getTelemetryInstance, Telemetry } from \"./telemetry\";\nimport { cloneTrashRepo, loadQualityDefinitionFromTrash, loadTrashCFConflicts, transformTrashQDs } from \"./trash-guide\";\nimport { ArrType } from \"./types/common.types\";\nimport { InputConfigArrInstance, InputConfigSchema } from \"./types/config.types\";\nimport { TrashArrSupported } from \"./types/trashguide.types\";\nimport { TrashArrSupportedConst, TrashQualityDefinition, TrashQualityDefinitionQuality } from \"./types/trashguide.types\";\nimport { isInConstArray } from \"./util\";\nimport { syncRootFolders } from \"./rootFolder/rootFolderSyncer\";\n\nconst pipeline = async (globalConfig: InputConfigSchema, instanceConfig: InputConfigArrInstance, arrType: ArrType) => {\n  const api = getUnifiedClient();\n\n  const system = await api.getSystemStatus();\n  logger.info(`System status: ${JSON.stringify(system)}`);\n\n  const serverCFs = await loadServerCustomFormats();\n  const serverQD = await loadQualityDefinitionFromServer();\n  const languages = await api.getLanguages();\n\n  const serverCache = new ServerCache(serverQD, [], serverCFs, languages);\n\n  logger.info(`Server objects: CustomFormats ${serverCFs.length}`);\n\n  const { config } = await mergeConfigsAndTemplates(globalConfig, instanceConfig, arrType);\n\n  if (Telemetry.isEnabled()) {\n    getTelemetryInstance().trackInstanceConfig(config, arrType);\n  }\n\n  const idsToManage = calculateCFsToManage(config);\n  logger.debug(Array.from(idsToManage), `CustomFormats to manage`);\n\n  const mergedCFs = await loadCustomFormatDefinitions(idsToManage, arrType, config.customFormatDefinitions || []);\n\n  // Check for conflicting CFs from TRaSH guides\n  if (isInConstArray(TrashArrSupportedConst, arrType) && !globalConfig.silenceTrashConflictWarnings) {\n    const conflicts = await loadTrashCFConflicts(arrType as TrashArrSupported);\n    checkForConflictingCFs(mergedCFs, config, conflicts);\n  }\n\n  const serverCFMapping = serverCache.cf.reduce((p, c) => {\n    p.set(c.name!, c);\n    return p;\n  }, new Map<string, MergedCustomFormatResource>());\n\n  const cfUpdateResult = await manageCf(mergedCFs, serverCFMapping);\n\n  // add missing CFs to list because we need it for further steps\n  // serverCFs.push(...cfUpdateResult.createCFs);\n  if (cfUpdateResult.createCFs.length > 0 || cfUpdateResult.updatedCFs.length > 0) {\n    // refresh cfs\n    serverCache.cf = await loadServerCustomFormats();\n  }\n\n  if (config.delete_unmanaged_custom_formats?.enabled) {\n    const idToCf = mergedCFs.carrIdMapping;\n\n    const mm = Array.from(idsToManage).reduce((p, c) => {\n      const cfName = idToCf.get(c)?.carrConfig.name;\n      if (cfName != null) {\n        p.set(cfName, true);\n      }\n      return p;\n    }, new Map<string, boolean>());\n\n    config.delete_unmanaged_custom_formats.ignore?.forEach((e) => {\n      mm.set(e, true);\n    });\n\n    const cfsToDelete = serverCache.cf.filter((e) => (e.name && mm.get(e.name)) !== true);\n\n    if (cfsToDelete.length > 0) {\n      if (getEnvs().DRY_RUN) {\n        logger.info(`DryRun: Would delete CF: ${cfsToDelete.map((e) => e.name).join(\", \")}`);\n      } else {\n        logger.info(`Deleting ${cfsToDelete.length} CustomFormats ...`);\n        logger.debug(\n          cfsToDelete.map((e) => e.name),\n          `This CustomFormats will be deleted:`,\n        );\n\n        for (const element of cfsToDelete) {\n          await deleteCustomFormat(element);\n        }\n      }\n    }\n  }\n\n  logger.info(`CustomFormats synchronized`);\n\n  // load tags\n  const serverTags = await loadServerTags();\n  serverCache.tags = serverTags;\n\n  if (config.quality_definition != null) {\n    const mergedQDs: TrashQualityDefinitionQuality[] = [];\n    const qualityDefinitionType = config.quality_definition.type;\n\n    // TODO: maybe add id reference as usage\n    if (qualityDefinitionType != null) {\n      if (!isInConstArray(TrashArrSupportedConst, arrType)) {\n        logger.warn(`QualityDefinition type is not supported for ${arrType} (${qualityDefinitionType}).`);\n      } else {\n        try {\n          let qdTrash: TrashQualityDefinition = await loadQualityDefinitionFromTrash(qualityDefinitionType, arrType);\n          const transformed = transformTrashQDs(qdTrash, config.quality_definition?.preferred_ratio);\n          mergedQDs.push(...transformed);\n        } catch (e: unknown) {\n          if (e instanceof Error) {\n            logger.error(e.message);\n          } else {\n            throw e;\n          }\n        }\n      }\n    } else {\n      logger.debug(`QualityDefinition: No TRaSH-Guide filename defined (type).`);\n    }\n\n    if (config.quality_definition.qualities) {\n      mergedQDs.push(...config.quality_definition.qualities);\n    }\n\n    const { changeMap, restData } = calculateQualityDefinitionDiff(serverCache.qd, mergedQDs);\n\n    if (changeMap.size > 0) {\n      if (getEnvs().DRY_RUN) {\n        logger.info(\"DryRun: Would update QualityDefinitions.\");\n      } else {\n        logger.info(`Diffs in quality definitions found ${changeMap.values()}`);\n        await api.updateQualityDefinitions(restData);\n        // refresh QDs\n        serverCache.qd = await loadQualityDefinitionFromServer();\n        logger.info(`Updated QualityDefinitions`);\n      }\n    } else {\n      logger.info(`QualityDefinitions do not need update!`);\n    }\n  } else {\n    logger.debug(`No QualityDefinition configured.`);\n  }\n\n  const namingDiff = await calculateNamingDiff(config.media_naming_api);\n\n  if (namingDiff) {\n    if (getEnvs().DRY_RUN) {\n      logger.info(\"DryRun: Would update MediaNaming.\");\n    } else {\n      // TODO this will need a radarr/sonarr separation for sure to have good and correct typings\n      await api.updateNaming(namingDiff.updatedData.id! + \"\", namingDiff.updatedData as any); // Ignore types\n      logger.info(`Updated MediaNaming`);\n    }\n  }\n\n  const managementDiff = await calculateMediamanagementDiff(config.media_management);\n\n  if (managementDiff) {\n    if (getEnvs().DRY_RUN) {\n      logger.info(\"DryRun: Would update MediaManagement.\");\n    } else {\n      // TODO this will need a radarr/sonarr separation for sure to have good and correct typings\n      await api.updateMediamanagement(managementDiff.updatedData.id! + \"\", managementDiff.updatedData as any); // Ignore types\n      logger.info(`Updated MediaManagement`);\n    }\n  }\n\n  await syncUiConfig(arrType, config.ui_config);\n\n  const serverQP = await loadQualityProfilesFromServer();\n  serverCache.qp = serverQP;\n\n  logger.info(`Server objects: QualityProfiles ${serverQP.length}`);\n\n  // calculate diff from server <-> what we want to be there\n  const { changedQPs, create, noChanges } = await calculateQualityProfilesDiff(arrType, mergedCFs, config, serverCache);\n\n  if (getEnvs().DEBUG_CREATE_FILES) {\n    create.concat(changedQPs).forEach((e, i) => {\n      fs.writeFileSync(`debug/test${i}.json`, JSON.stringify(e, null, 2), \"utf-8\");\n    });\n  }\n\n  logger.info(`QualityProfiles: Create: ${create.length}, Update: ${changedQPs.length}, Unchanged: ${noChanges.length}`);\n\n  if (!getEnvs().DRY_RUN) {\n    for (const element of create) {\n      try {\n        const newProfile = await api.createQualityProfile(element);\n        logger.info(`Created QualityProfile: ${newProfile.name}`);\n      } catch (error: any) {\n        logger.error(`Failed creating QualityProfile (${element.name})`);\n        throw error;\n      }\n    }\n\n    for (const element of changedQPs) {\n      try {\n        const newProfile = await api.updateQualityProfile(\"\" + element.id, element);\n        logger.info(`Updated QualityProfile: ${newProfile.name}`);\n      } catch (error: any) {\n        logger.error(`Failed updating QualityProfile (${element.name})`);\n        throw error;\n      }\n    }\n  } else if (create.length > 0 || changedQPs.length > 0) {\n    logger.info(\"DryRun: Would create/update QualityProfiles.\");\n  }\n\n  if (config.delete_unmanaged_quality_profiles?.enabled) {\n    const unmanagedQPs: MergedQualityProfileResource[] = getUnmanagedQualityProfiles(serverCache.qp, config.quality_profiles);\n\n    const ignoreSet = new Set(config.delete_unmanaged_quality_profiles.ignore ?? []);\n\n    const qpsToDelete: MergedQualityProfileResource[] = unmanagedQPs.filter((qp) => qp.name && !ignoreSet.has(qp.name));\n\n    if (qpsToDelete.length > 0) {\n      if (getEnvs().DRY_RUN) {\n        logger.info(`DryRun: Would delete QP: ${qpsToDelete.map((e) => e.name).join(\", \")}`);\n      } else {\n        logger.info(`Deleting ${qpsToDelete.length} QualityProfiles ...`);\n        logger.debug(\n          qpsToDelete.map((e) => e.name),\n          \"This QualityProfile will be deleted:\",\n        );\n        for (const element of qpsToDelete) {\n          await deleteQualityProfile(element);\n        }\n      }\n    }\n  }\n\n  // Handle metadata profiles (Lidarr / Readarr) - unified sync with optional deletion\n  await syncMetadataProfiles(arrType, config, serverCache);\n\n  await syncRootFolders(arrType, config.root_folders, serverCache);\n\n  // Handle delay profiles\n  if (\n    config.delay_profiles == null ||\n    (config.delay_profiles.default == null && (config.delay_profiles.additional == null || config.delay_profiles.additional.length === 0))\n  ) {\n    logger.debug(`Config 'delay_profiles' not specified. Ignoring.`);\n  } else {\n    const delayProfilesDiff = await calculateDelayProfilesDiff(config.delay_profiles, serverCache.tags);\n\n    if (delayProfilesDiff?.defaultProfileChanged || delayProfilesDiff?.additionalProfilesChanged) {\n      if (getEnvs().DRY_RUN) {\n        logger.info(\"DryRun: Would update DelayProfiles.\");\n      } else {\n        if (delayProfilesDiff.defaultProfileChanged && delayProfilesDiff.defaultProfile) {\n          logger.info(`Updating default DelayProfile`);\n          const mappedDefaultDelayProfile = mapToServerDelayProfile(delayProfilesDiff.defaultProfile, serverCache.tags);\n          await api.updateDelayProfile(\"1\", mappedDefaultDelayProfile);\n        }\n\n        if (delayProfilesDiff.missingTags.length > 0) {\n          logger.info(`Creating missing tags on server: ${delayProfilesDiff.missingTags.join(\", \")}`);\n          try {\n            for (const tagName of delayProfilesDiff.missingTags) {\n              const newTag = await api.createTag({ label: tagName });\n              serverCache.tags.push(newTag);\n            }\n          } catch (err: any) {\n            logger.error(`Failed creating tags: ${err.message}`);\n            throw err;\n          }\n        }\n\n        if (delayProfilesDiff.additionalProfilesChanged && delayProfilesDiff.additionalProfiles) {\n          logger.info(`Updating additional DelayProfiles (deleting old ones and recreate all) ...`);\n\n          await deleteAdditionalDelayProfiles();\n\n          for (const profile of delayProfilesDiff.additionalProfiles) {\n            const mappedProfile = mapToServerDelayProfile(profile, serverCache.tags);\n            await api.createDelayProfile(mappedProfile); // Create or update profile\n          }\n        }\n\n        logger.info(`Successfully synched delay profiles.`);\n      }\n    }\n  }\n\n  // Download Clients\n  if (config.download_clients?.data || config.download_clients?.delete_unmanaged?.enabled) {\n    if (getEnvs().DRY_RUN) {\n      logger.info(\"DryRun: Would sync download clients.\");\n    } else {\n      try {\n        await syncDownloadClients(arrType, config, serverCache);\n      } catch (err: any) {\n        logger.error(`Failed to sync download clients: ${err.message}`);\n      }\n    }\n  }\n\n  // Download Client Configuration\n  if (config.download_clients?.config) {\n    if (getEnvs().DRY_RUN) {\n      logger.info(\"DryRun: Would sync download client config.\");\n    } else {\n      try {\n        await syncDownloadClientConfig(arrType, config, serverCache);\n      } catch (err: any) {\n        logger.error(`Failed to sync download client config: ${err.message}`);\n      }\n    }\n  }\n\n  // Sync remote path mappings\n  if (\n    config.download_clients?.remote_paths !== undefined &&\n    (config.download_clients.remote_paths.length > 0 || config.download_clients.delete_unmanaged_remote_paths)\n  ) {\n    logger.debug(`[DEBUG] About to sync remote paths for ${arrType}. Count: ${config.download_clients.remote_paths.length}`);\n    if (getEnvs().DRY_RUN) {\n      logger.info(\"DryRun: Would sync remote path mappings.\");\n    } else {\n      try {\n        await syncRemotePaths(arrType, config);\n      } catch (err: any) {\n        logger.error(`Failed to sync remote path mappings: ${err.message}`);\n      }\n    }\n  } else {\n    logger.debug(`[DEBUG] No remote paths to sync for ${arrType}. download_clients: ${JSON.stringify(config.download_clients)}`);\n  }\n};\n\nconst runArrType = async (\n  arrType: ArrType,\n  globalConfig: InputConfigSchema,\n  arrEntry: Record<string, InputConfigArrInstance> | undefined,\n) => {\n  const status = {\n    success: 0,\n    failure: 0,\n    skipped: 0,\n  };\n\n  if (!arrEntry || typeof arrEntry !== \"object\" || Object.keys(arrEntry).length === 0) {\n    logHeading(`No ${arrType} instances defined.`);\n    return status;\n  }\n\n  logHeading(`Processing ${arrType} ...`);\n\n  for (const [instanceName, instance] of Object.entries(arrEntry)) {\n    logInstanceHeading(`Processing ${arrType} Instance: ${instanceName} ...`);\n\n    if (instance.enabled === false) {\n      logger.info(`Instance ${arrType} - ${instanceName} is disabled!`);\n      status.skipped++;\n      continue;\n    }\n\n    try {\n      await configureApi(arrType, instance.base_url, instance.api_key);\n      await pipeline(globalConfig, instance, arrType);\n      status.success++;\n    } catch (err: any) {\n      logger.error(\n        `Failure during configuring: ${arrType} - ${instanceName} (Detailed logs with env var: LOG_STACKTRACE=true). Error: ${err?.message}`,\n      );\n      status.failure++;\n      if (getEnvs().LOG_STACKTRACE) {\n        logger.error(err);\n      }\n      if (getEnvs().STOP_ON_ERROR) {\n        throw new Error(`Stopping further execution because 'STOP_ON_ERROR' is enabled.`);\n      }\n    } finally {\n      unsetApi();\n    }\n\n    logger.info(\"\");\n  }\n\n  return status;\n};\n\nconst run = async () => {\n  logger.info(`Support the project: https://ko-fi.com/blackdark93 - Star on Github! https://github.com/raydak-labs/configarr`);\n  logger.info(`Configarr Version: ${getEnvs().CONFIGARR_VERSION}`);\n\n  const buildInfo = getBuildInfo();\n  const shaDisplay = buildInfo.githubSha ? buildInfo.githubSha.slice(0, 7) : \"unknown\";\n  logger.debug(`Build Info: ${buildInfo.buildTime || \"unknown\"} | ${shaDisplay} | (run id) ${buildInfo.githubRunId || \"unknown\"}`);\n\n  if (getEnvs().DRY_RUN) {\n    logger.info(\"DryRun: Running in dry-run mode!\");\n  }\n\n  const globalConfig = getConfig();\n\n  await cloneRecyclarrTemplateRepo();\n  await cloneTrashRepo();\n\n  const totalStatus: string[] = [];\n\n  const disabledArrs: string[] = [];\n\n  const arrTypes = [\n    { type: \"SONARR\", enabled: globalConfig.sonarrEnabled, config: globalConfig.sonarr },\n    { type: \"RADARR\", enabled: globalConfig.radarrEnabled, config: globalConfig.radarr },\n    { type: \"WHISPARR\", enabled: globalConfig.whisparrEnabled, config: globalConfig.whisparr },\n    { type: \"READARR\", enabled: globalConfig.readarrEnabled, config: globalConfig.readarr },\n    { type: \"LIDARR\", enabled: globalConfig.lidarrEnabled, config: globalConfig.lidarr },\n  ];\n\n  // Initialize telemetry\n  if (Telemetry.isEnabled({ enabled: globalConfig.telemetry })) {\n    // Collect all instances for telemetry\n    const allInstances: Record<string, InputConfigArrInstance[]> = {};\n    for (const { type, config } of arrTypes) {\n      if (config) {\n        allInstances[type] = Object.values(config);\n      } else {\n        allInstances[type] = [];\n      }\n    }\n\n    getTelemetryInstance().trackFeatureUsage(globalConfig, allInstances);\n  }\n\n  for (const { type, enabled, config } of arrTypes) {\n    if (enabled == null || enabled) {\n      const result = await runArrType(type as ArrType, globalConfig, config);\n      totalStatus.push(`${type}: (${result.success}/${result.failure}/${result.skipped})`);\n    } else {\n      logger.debug(`${type} disabled in config`);\n      disabledArrs.push(type);\n    }\n  }\n\n  logger.info(``);\n  if (disabledArrs.length > 0) {\n    logger.info(`Disabled Arrs: ${disabledArrs.join(\", \")}`);\n  }\n  logger.info(`Execution Summary (success/failure/skipped) instances: ${totalStatus.join(\" - \")}`);\n\n  if (Telemetry.isEnabled()) {\n    await getTelemetryInstance().finalizeTracking();\n  }\n};\n\nrun();\n"
  },
  {
    "path": "src/ky-client.test.ts",
    "content": "import { beforeEach, describe, expect, test, vi } from \"vitest\";\nimport kyDefault, { HTTPError, NormalizedOptions } from \"ky\";\nimport { HttpClient } from \"./ky-client\";\n\nvi.mock(\"ky\", async (importOriginal) => {\n  const actual = await importOriginal<typeof import(\"ky\")>();\n  return {\n    ...actual,\n    default: {\n      create: vi.fn(),\n    },\n  };\n});\n\nvi.mock(\"./logger\", () => ({\n  logger: { debug: vi.fn(), error: vi.fn(), warn: vi.fn(), info: vi.fn() },\n}));\n\nvi.mock(\"./clients/unified-client\", () => ({\n  createConnectionErrorParts: vi.fn().mockReturnValue([]),\n}));\n\nconst makeHTTPError = (status: number, statusText: string, body: string, contentType: string) => {\n  const response = new Response(body, { status, statusText, headers: { \"Content-Type\": contentType } });\n  return new HTTPError(response, new Request(\"http://localhost/test\"), {} as NormalizedOptions);\n};\n\ndescribe(\"HttpClient error handling\", () => {\n  let mockKyFn: ReturnType<typeof vi.fn>;\n  let client: HttpClient;\n\n  beforeEach(() => {\n    vi.clearAllMocks();\n    mockKyFn = vi.fn();\n    vi.mocked(kyDefault.create).mockReturnValue(mockKyFn as any);\n    client = new HttpClient({ prefixUrl: \"http://localhost:7878\" });\n  });\n\n  describe(\"JSON error responses\", () => {\n    test(\"extracts messages from array response\", async () => {\n      const error = makeHTTPError(\n        400,\n        \"Bad Request\",\n        JSON.stringify([{ message: \"First error\" }, { message: \"Second error\" }]),\n        \"application/json\",\n      );\n      mockKyFn.mockRejectedValueOnce(error);\n\n      await expect(client.request({ path: \"/api/test\", method: \"GET\" })).rejects.toThrow(\"First error, Second error\");\n    });\n\n    test(\"extracts errorMessage field from array items\", async () => {\n      const error = makeHTTPError(422, \"Unprocessable Entity\", JSON.stringify([{ errorMessage: \"Validation failed\" }]), \"application/json\");\n      mockKyFn.mockRejectedValueOnce(error);\n\n      await expect(client.request({ path: \"/api/test\", method: \"GET\" })).rejects.toThrow(\"Validation failed\");\n    });\n\n    test(\"falls back to JSON dump when array items have no message fields (no 'undefined' in output)\", async () => {\n      const body = JSON.stringify([{ code: 123 }, { code: 456 }]);\n      const error = makeHTTPError(400, \"Bad Request\", body, \"application/json\");\n      mockKyFn.mockRejectedValueOnce(error);\n\n      await expect(client.request({ path: \"/api/test\", method: \"GET\" })).rejects.toThrow('[{\"code\":123},{\"code\":456}]');\n    });\n\n    test(\"extracts message from object response\", async () => {\n      const error = makeHTTPError(401, \"Unauthorized\", JSON.stringify({ message: \"Invalid API key\" }), \"application/json\");\n      mockKyFn.mockRejectedValueOnce(error);\n\n      await expect(client.request({ path: \"/api/test\", method: \"GET\" })).rejects.toThrow(\"Invalid API key\");\n    });\n\n    test(\"extracts errorMessage field from object response\", async () => {\n      const error = makeHTTPError(400, \"Bad Request\", JSON.stringify({ errorMessage: \"Resource not found\" }), \"application/json\");\n      mockKyFn.mockRejectedValueOnce(error);\n\n      await expect(client.request({ path: \"/api/test\", method: \"GET\" })).rejects.toThrow(\"Resource not found\");\n    });\n\n    test(\"handles invalid JSON despite application/json content-type\", async () => {\n      const error = makeHTTPError(500, \"Internal Server Error\", \"not valid json {{{\", \"application/json\");\n      mockKyFn.mockRejectedValueOnce(error);\n\n      await expect(client.request({ path: \"/api/test\", method: \"GET\" })).rejects.toThrow(\"Failed to read response body\");\n    });\n  });\n\n  describe(\"non-JSON HTTP errors\", () => {\n    test(\"includes HTTP status in message for non-JSON responses\", async () => {\n      const error = makeHTTPError(503, \"Service Unavailable\", \"<html>Down</html>\", \"text/html\");\n      mockKyFn.mockRejectedValueOnce(error);\n\n      await expect(client.request({ path: \"/api/test\", method: \"GET\" })).rejects.toThrow(\"HTTP Error: 503 Service Unavailable\");\n    });\n\n    test(\"no trailing punctuation when no additional context\", async () => {\n      const error = makeHTTPError(404, \"Not Found\", \"Not found\", \"text/plain\");\n      mockKyFn.mockRejectedValueOnce(error);\n\n      const thrown = await client.request({ path: \"/api/test\", method: \"GET\" }).catch((e: Error) => e);\n      expect((thrown as Error).message).toBe(\"HTTP Error: 404 Not Found\");\n    });\n  });\n\n  describe(\"connection errors\", () => {\n    test(\"TypeError produces a user-friendly message\", async () => {\n      const typeError = new TypeError(\"fetch failed\");\n      mockKyFn.mockRejectedValueOnce(typeError);\n\n      await expect(client.request({ path: \"/api/test\", method: \"GET\" })).rejects.toThrow(/connection issues/i);\n    });\n\n    test(\"TypeError includes cause details when available\", async () => {\n      const cause = new Error(\"connect ECONNREFUSED 127.0.0.1:7878\");\n      const typeError = new TypeError(\"fetch failed\", { cause });\n      mockKyFn.mockRejectedValueOnce(typeError);\n\n      await expect(client.request({ path: \"/api/test\", method: \"GET\" })).rejects.toThrow(/ECONNREFUSED/);\n    });\n  });\n});\n"
  },
  {
    "path": "src/ky-client.ts",
    "content": "// Copied and modified from here: https://github.com/acacode/swagger-typescript-api/pull/690\nimport type { BeforeRequestHook, Hooks, KyInstance, Options as KyOptions, NormalizedOptions } from \"ky\";\nimport ky, { HTTPError } from \"ky\";\nimport { logger } from \"./logger\";\nimport { createConnectionErrorParts } from \"./clients/unified-client\";\n\nfunction toErrorMessage(value: unknown): string {\n  if (value === null) return \"null\";\n  if (value === undefined) return \"undefined\";\n  if (typeof value === \"string\") return value;\n  if (typeof value === \"object\") return JSON.stringify(value);\n  return String(value);\n}\n\ntype KyResponse<Data> = Response & {\n  json<T extends Data = Data>(): Promise<T>;\n};\n\nexport type ResponsePromise<Data> = {\n  arrayBuffer: () => Promise<ArrayBuffer>;\n  blob: () => Promise<Blob>;\n  formData: () => Promise<FormData>;\n  json<T extends Data = Data>(): Promise<T>;\n  text: () => Promise<string>;\n} & Promise<KyResponse<Data>>;\n\nexport type ResponseFormat = keyof Omit<Body, \"body\" | \"bodyUsed\">;\n\n// Same as axios. Using provided SearchParamsOption by ky break some typings\nexport type QueryParamsType = Record<string | number, unknown>;\n\nexport interface FullRequestParams extends Omit<KyOptions, \"json\" | \"body\" | \"searchParams\"> {\n  /** set parameter to `true` for call `securityWorker` for this request */\n  secure?: boolean;\n  /** request path */\n  path: string;\n  /** content type of request body */\n  type?: ContentType;\n  /** query params */\n  query?: QueryParamsType;\n  /** format of response (i.e. response.json() -> format: \"json\") */\n  format?: ResponseFormat;\n  /** request body */\n  body?: unknown;\n}\n\nexport type RequestParams = Omit<FullRequestParams, \"body\" | \"method\" | \"query\" | \"path\">;\n\nexport interface ApiConfig<SecurityDataType = unknown> extends Omit<KyOptions, \"data\" | \"cancelToken\"> {\n  securityWorker?: (securityData: SecurityDataType | null) => Promise<NormalizedOptions | void> | NormalizedOptions | void;\n  secure?: boolean;\n  format?: ResponseFormat;\n}\n\nexport enum ContentType {\n  Json = \"application/json\",\n  FormData = \"multipart/form-data\",\n  UrlEncoded = \"application/x-www-form-urlencoded\",\n  Text = \"text/plain\",\n}\n\nexport class HttpClient<SecurityDataType = unknown> {\n  public ky: KyInstance;\n  private securityData: SecurityDataType | null = null;\n  private securityWorker?: ApiConfig<SecurityDataType>[\"securityWorker\"];\n  private secure?: boolean;\n  private format?: ResponseFormat;\n\n  constructor({ securityWorker, secure, format, ...options }: ApiConfig<SecurityDataType> = {}) {\n    this.ky = ky.create({ ...options, prefixUrl: options.prefixUrl || \"\" });\n    this.secure = secure;\n    this.format = format;\n    this.securityWorker = securityWorker;\n  }\n\n  public setSecurityData = (data: SecurityDataType | null) => {\n    this.securityData = data;\n  };\n\n  protected mergeRequestParams(params1: KyOptions, params2?: KyOptions): KyOptions {\n    return {\n      ...params1,\n      ...params2,\n      headers: {\n        ...params1.headers,\n        ...(params2 && params2.headers),\n      },\n    };\n  }\n\n  protected stringifyFormItem(formItem: unknown) {\n    if (typeof formItem === \"object\" && formItem !== null) {\n      return JSON.stringify(formItem);\n    } else {\n      return `${formItem}`;\n    }\n  }\n\n  protected createFormData(input: Record<string, unknown>): FormData {\n    return Object.keys(input || {}).reduce((formData, key) => {\n      const property = input[key];\n      const propertyContent: unknown[] = property instanceof Array ? property : [property]; // Changed `any[]` to `unknown[]`\n\n      for (const formItem of propertyContent) {\n        const isFileType = formItem instanceof Blob || formItem instanceof File;\n        formData.append(key, isFileType ? formItem : this.stringifyFormItem(formItem));\n      }\n\n      return formData;\n    }, new FormData());\n  }\n\n  public request = async <T = any, _E = any>({\n    secure = this.secure,\n    path,\n    type,\n    query,\n    format,\n    body,\n    ...options\n  }: FullRequestParams): Promise<T> => {\n    if (body) {\n      if (type === ContentType.FormData) {\n        body = typeof body === \"object\" ? this.createFormData(body as Record<string, unknown>) : body;\n      } else if (type === ContentType.Text) {\n        body = typeof body !== \"string\" ? JSON.stringify(body) : body;\n      }\n    }\n\n    let headers: Headers | Record<string, string | undefined> | undefined;\n    if (options.headers instanceof Headers) {\n      headers = new Headers(options.headers);\n      if (type && type !== ContentType.FormData) {\n        headers.set(\"Content-Type\", type);\n      }\n    } else {\n      headers = { ...options.headers } as Record<string, string | undefined>;\n      if (type && type !== ContentType.FormData) {\n        headers[\"Content-Type\"] = type;\n      }\n    }\n\n    let hooks: Hooks | undefined;\n    if (secure && this.securityWorker) {\n      const securityWorker: BeforeRequestHook = async (request, options) => {\n        const secureOptions = await this.securityWorker!(this.securityData);\n        if (secureOptions && typeof secureOptions === \"object\") {\n          let { headers } = options;\n          if (secureOptions.headers) {\n            const mergedHeaders = new Headers(headers);\n            const secureHeaders = new Headers(secureOptions.headers);\n            secureHeaders.forEach((value, key) => {\n              mergedHeaders.set(key, value);\n            });\n            headers = mergedHeaders;\n          }\n          return new Request(request.url, {\n            ...options,\n            ...secureOptions,\n            headers,\n          });\n        }\n      };\n\n      hooks = {\n        ...options.hooks,\n        beforeRequest: options.hooks && options.hooks.beforeRequest ? [securityWorker, ...options.hooks.beforeRequest] : [securityWorker],\n      };\n    }\n\n    let searchParams: URLSearchParams | undefined;\n\n    if (query != null) {\n      searchParams = new URLSearchParams(query as Record<string, string>);\n    }\n\n    // // Workaround for not working POSTs. Use JSON when json type else body\n    // const data: any = {};\n\n    // if (type == ContentType.Json) {\n    //   data.json = body;\n    // } else {\n    //   data.body = body as BodyInit;\n    // }\n\n    try {\n      const requestPromise = await this.ky<T>(path.replace(/^\\//, \"\"), {\n        ...options,\n        headers,\n        searchParams,\n        //...data,\n        // Use always JSON\n        json: body as BodyInit,\n        hooks,\n      });\n\n      // 2025-02-09: Added workaround for delete stuff\n      if (options.method === \"DELETE\") {\n        if (requestPromise.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n          return requestPromise.json();\n        } else {\n          return requestPromise.statusText as T;\n        }\n      }\n\n      return requestPromise.json();\n    } catch (error: any) {\n      logger.debug(`Error during request with error: ${error?.name}`);\n\n      // Use createConnectionErrorParts for consistent error handling\n      const errorParts = createConnectionErrorParts(error);\n      const [friendlyMessage, structuredMessage, rawMessage] = errorParts;\n      const enhancedMessage = structuredMessage || friendlyMessage || rawMessage;\n\n      if (error instanceof HTTPError) {\n        const { response, request } = error;\n\n        if (response) {\n          const contentType = response.headers.get(\"content-type\");\n\n          if (contentType && contentType.includes(\"application/json\")) {\n            let errorJson: unknown;\n            try {\n              errorJson = await response.json();\n            } catch {\n              const message = `HTTP Error: ${response.status} ${response.statusText}. Failed to read response body.`;\n              logger.error(message);\n              throw new Error(message, { cause: error });\n            }\n\n            let errorMessage = \"unknown error\";\n\n            if (Array.isArray(errorJson)) {\n              const messages = (errorJson as Array<Record<string, unknown>>)\n                .map((e) => {\n                  const msg = e[\"message\"] ?? e[\"errorMessage\"];\n                  return typeof msg === \"string\" && msg ? msg : undefined;\n                })\n                .filter((m): m is string => m !== undefined);\n              errorMessage = messages.length > 0 ? messages.join(\", \") : JSON.stringify(errorJson);\n            } else if (errorJson && typeof errorJson === \"object\") {\n              const errObj = errorJson as Record<string, unknown>;\n              const msg = errObj[\"message\"] ?? errObj[\"errorMessage\"];\n              if (typeof msg === \"string\" && msg) errorMessage = msg;\n            }\n\n            logger.error(errorJson, `Failed executing request: '${errorMessage}'`);\n            throw new Error(errorMessage, { cause: error });\n          } else {\n            const messageParts = [`HTTP Error: ${response.status} ${response.statusText}`];\n            if (enhancedMessage) messageParts.push(enhancedMessage);\n            const message = messageParts.join(\". \");\n            logger.error(message);\n            throw new Error(message, { cause: error });\n          }\n        } else if (request) {\n          const message = `Request failed (no response). ${enhancedMessage || \"Probably connection issues.\"}`;\n          logger.error(message);\n          throw new Error(message, { cause: error });\n        } else {\n          const message = `Request setup failed: ${enhancedMessage || \"Unknown error\"}`;\n          logger.error(message);\n          throw new Error(message, { cause: error });\n        }\n      } else if (error instanceof TypeError) {\n        let errorMessage = \"Probably some connection issues. If not, feel free to open an issue with details to improve handling.\";\n\n        if (error.cause && error.cause instanceof Error) {\n          errorMessage += ` Caused by: '${error.cause.message}'.`;\n\n          if (\"code\" in error.cause) {\n            errorMessage += ` Error code: '${error.cause.code}'.`;\n          }\n        }\n\n        if (enhancedMessage && !errorMessage.includes(enhancedMessage)) {\n          errorMessage += ` ${enhancedMessage}`;\n        }\n\n        logger.error(errorMessage);\n        throw new Error(errorMessage, { cause: error });\n      } else {\n        const message = `Unexpected error: ${enhancedMessage || toErrorMessage(error)}`;\n        logger.error(message);\n        throw new Error(message);\n      }\n    }\n  };\n}\n\nexport const KyHttpClient = HttpClient;\n"
  },
  {
    "path": "src/local-importer.ts",
    "content": "import { default as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport yaml from \"yaml\";\nimport { getConfig } from \"./config\";\nimport { logger } from \"./logger\";\nimport { ArrType, MappedTemplates } from \"./types/common.types\";\nimport { RecyclarrTemplates } from \"./types/recyclarr.types\";\n\nexport const getLocalTemplatePath = () => {\n  const config = getConfig();\n\n  if (config.localConfigTemplatesPath == null) {\n    logger.debug(`No local templates specified. Skipping.`);\n    return null;\n  }\n\n  const customPath = path.resolve(config.localConfigTemplatesPath);\n\n  if (!fs.existsSync(customPath)) {\n    logger.info(`Provided local templates path '${config.localConfigTemplatesPath}' does not exist.`);\n    return null;\n  }\n\n  return customPath;\n};\n\nexport const loadLocalRecyclarrTemplate = (arrType: ArrType): Map<string, MappedTemplates> => {\n  const map = new Map<string, RecyclarrTemplates>();\n\n  const fillMap = (path: string) => {\n    const files = fs.readdirSync(`${path}`).filter((fn) => fn.endsWith(\"yaml\") || fn.endsWith(\"yml\"));\n\n    files.forEach((f) => {\n      const fileContent = yaml.parse(fs.readFileSync(`${path}/${f}`, \"utf8\"));\n\n      if (fileContent == null) {\n        logger.warn(`Local template content of '${f}' is empty. Ignoring.`);\n      } else {\n        map.set(f.substring(0, f.lastIndexOf(\".\")), fileContent);\n      }\n    });\n  };\n\n  const localPath = getLocalTemplatePath();\n\n  if (localPath) {\n    fillMap(localPath);\n  }\n\n  logger.debug(`Found ${map.size} local templates.`);\n\n  return new Map(\n    Array.from(map, ([k, v]) => {\n      const customFormats = v.custom_formats?.map((cf) => {\n        // Changes from Recyclarr 7.2.0: https://github.com/recyclarr/recyclarr/releases/tag/v7.2.0\n        if (cf.assign_scores_to == null && cf.quality_profiles == null) {\n          logger.warn(`Local Template \"${k}\" does not provide correct profile for custom format. Ignoring.`);\n        }\n\n        if (cf.quality_profiles) {\n          logger.warn(\n            `Deprecated: (Local Template '${k}') For custom_formats please rename 'quality_profiles' to 'assign_scores_to'. See recyclarr v7.2.0`,\n          );\n        }\n        return { ...cf, assign_scores_to: cf.assign_scores_to ?? cf.quality_profiles ?? [] };\n      });\n\n      return [\n        k,\n        {\n          ...v,\n          custom_formats: customFormats,\n        },\n      ];\n    }),\n  );\n};\n"
  },
  {
    "path": "src/logger.ts",
    "content": "import { levels, pino } from \"pino\";\nimport pretty from \"pino-pretty\";\nimport { getEnvs, getHelpers } from \"./env\";\n\nconst maxArrayLength = Object.values(levels.labels).reduce((maxLength, currentArray) => {\n  return Math.max(maxLength, currentArray.length);\n}, 0);\n\n// Function to format a level string by appending spaces\nconst neededSpaces = (level: string): string => {\n  const spacesNeeded = maxArrayLength - level.length;\n  const spaces = \" \".repeat(spacesNeeded > 0 ? spacesNeeded : 0);\n  return `${spaces}`;\n};\n\nconst transformedLevelMap = Object.entries(levels.values).reduce((p, [key, value]) => {\n  p.set(key.toUpperCase(), neededSpaces(key));\n\n  return p;\n}, new Map<string, string>());\n\nconst stream = pretty({\n  levelFirst: true,\n  ignore: \"hostname,pid\",\n  // @ts-ignore Temporary weird error\n  customPrettifiers: {\n    level: (level, key, log, { colors, label, labelColorized }) => `${labelColorized}${transformedLevelMap.get(label)}`,\n  },\n});\n\nexport const logger = pino(\n  {\n    level: getEnvs().LOG_LEVEL,\n    // transport: {\n    //   target: \"pino-pretty\",\n    //   options: {\n    //     colorize: true,\n    //   },\n    // },\n  },\n  stream,\n);\n\nexport const logSeparator = () => {\n  logger.info(`#############################################`);\n};\n\nexport const logHeading = (title: string) => {\n  logger.info(\"\");\n  logSeparator();\n  logger.info(`### ${title}`);\n  logSeparator();\n  logger.info(\"\");\n};\n\nexport const logInstanceHeading = (title: string) => {\n  logger.info(`### ${title}`);\n};\n\n// For debugging how envs have been loaded\nlogger.debug({ envs: getEnvs(), helpers: getHelpers() }, `Loaded following configuration from ENVs and mapped`);\n"
  },
  {
    "path": "src/media-management.ts",
    "content": "import { getUnifiedClient } from \"./clients/unified-client\";\nimport { logger } from \"./logger\";\nimport { MediaManagementType, MediaNamingApiType } from \"./types/config.types\";\nimport { compareMediamanagement, compareNaming } from \"./util\";\n\nconst loadNamingFromServer = async () => {\n  const api = getUnifiedClient();\n  const result = await api.getNaming();\n  return result;\n};\n\nconst loadMediamanagementConfigFromServer = async () => {\n  const api = getUnifiedClient();\n  const result = await api.getMediamanagement();\n  return result;\n};\n\nexport const calculateNamingDiff = async (mediaNaming?: MediaNamingApiType) => {\n  if (mediaNaming == null) {\n    logger.debug(`Config 'media_naming_api' not specified. Ignoring.`);\n    return null;\n  }\n\n  const serverData = await loadNamingFromServer();\n\n  const { changes, equal } = compareNaming(serverData, mediaNaming);\n\n  if (equal) {\n    logger.debug(`Media naming API settings are in sync`);\n    return null;\n  }\n\n  logger.info(`Found ${changes.length} differences for media naming api.`);\n  logger.debug(changes, `Found following changes for media naming api`);\n\n  return {\n    changes,\n    updatedData: {\n      ...serverData,\n      ...mediaNaming,\n    },\n  };\n};\n\nexport const calculateMediamanagementDiff = async (mediaManagement?: MediaManagementType) => {\n  if (mediaManagement == null) {\n    logger.debug(`Config 'media_management' not specified. Ignoring.`);\n    return null;\n  }\n\n  const serverData = await loadMediamanagementConfigFromServer();\n\n  logger.debug(serverData, \"Media Server\");\n  logger.debug(mediaManagement, \"Media Local\");\n  const { changes, equal } = compareMediamanagement(serverData, mediaManagement);\n\n  if (equal) {\n    logger.debug(`Media management settings are in sync`);\n    return null;\n  }\n\n  logger.info(`Found ${changes.length} differences for media management.`);\n  logger.debug(changes, `Found following changes for media management`);\n\n  return {\n    changes,\n    updatedData: {\n      ...serverData,\n      ...mediaManagement,\n    },\n  };\n};\n"
  },
  {
    "path": "src/metadataProfiles/metadataProfile.types.ts",
    "content": "import { InputConfigMetadataProfile } from \"../types/config.types\";\n\n// Common interface for all metadata profile resources\n// All metadata profiles must have at least id and name\nexport interface BaseMetadataProfileResource {\n  id?: number;\n  name?: string | null;\n}\n\n// Shared types for metadata profile operations\n// Generic type T represents the specific MetadataProfileResource type (Lidarr, Readarr, etc.)\nexport interface MetadataProfileDiff<T extends BaseMetadataProfileResource = any> {\n  missingOnServer: InputConfigMetadataProfile[];\n  changed: Array<{ config: InputConfigMetadataProfile; server: T }>;\n  noChanges: T[];\n}\n\nexport interface MetadataProfileSyncResult {\n  added: number;\n  removed: number;\n  updated: number;\n}\n"
  },
  {
    "path": "src/metadataProfiles/metadataProfileBase.test.ts",
    "content": "import { describe, it, expect } from \"vitest\";\n\ndescribe(\"BaseMetadataProfileSync\", () => {\n  it(\"should be an abstract class that requires implementation\", () => {\n    // This is an abstract class - concrete implementations are tested separately\n    // in metadataProfileLidarr.test.ts and metadataProfileReadarr.test.ts\n    expect(true).toBe(true);\n  });\n});\n"
  },
  {
    "path": "src/metadataProfiles/metadataProfileBase.ts",
    "content": "import { ServerCache } from \"../cache\";\nimport { getUnifiedClient, IArrClient } from \"../clients/unified-client\";\nimport { getEnvs } from \"../env\";\nimport { logger } from \"../logger\";\nimport { ArrType } from \"../types/common.types\";\nimport { InputConfigMetadataProfile, MergedConfigInstance } from \"../types/config.types\";\nimport { BaseMetadataProfileResource, MetadataProfileDiff, MetadataProfileSyncResult } from \"./metadataProfile.types\";\n\n// Base class for metadata profile synchronization\nexport abstract class BaseMetadataProfileSync<T extends BaseMetadataProfileResource = any> {\n  protected api: IArrClient = getUnifiedClient();\n  protected logger = logger;\n\n  protected abstract loadFromServer(): Promise<T[]>;\n  protected abstract getArrType(): ArrType;\n\n  protected abstract createMetadataProfile(resolvedConfig: T): Promise<T>;\n  protected abstract updateMetadataProfile(id: string, resolvedConfig: T): Promise<T>;\n  protected abstract deleteProfile(id: string): Promise<void>;\n\n  abstract calculateDiff(profiles: InputConfigMetadataProfile[], serverCache: ServerCache): Promise<MetadataProfileDiff<T> | null>;\n\n  public abstract resolveConfig(config: InputConfigMetadataProfile, serverCache: ServerCache): Promise<T>;\n\n  /**\n   * Sync metadata profiles - handles add/update and optional deletion\n   */\n  async syncMetadataProfiles(config: MergedConfigInstance, serverCache: ServerCache): Promise<MetadataProfileSyncResult> {\n    const profiles = config.metadata_profiles || [];\n    const deleteConfig = config.delete_unmanaged_metadata_profiles;\n\n    // Step 1: Perform sync (add/update)\n    const syncResult = await this.performSync(profiles, serverCache);\n\n    // Step 2: Handle deletion if requested\n    let removed = 0;\n    if (deleteConfig) {\n      removed = await this.performDeletion(profiles, deleteConfig);\n    }\n\n    // Combine results\n    const totalChanges = syncResult.added + syncResult.updated + removed;\n    if (totalChanges > 0) {\n      this.logger.info(`Updated MetadataProfiles: +${syncResult.added} ~${syncResult.updated} -${removed}`);\n    }\n\n    return {\n      added: syncResult.added,\n      updated: syncResult.updated,\n      removed,\n    };\n  }\n\n  private async performSync(profiles: InputConfigMetadataProfile[], serverCache: ServerCache): Promise<{ added: number; updated: number }> {\n    const diff = await this.calculateDiff(profiles, serverCache);\n\n    if (!diff) {\n      return { added: 0, updated: 0 };\n    }\n\n    if (getEnvs().DRY_RUN) {\n      this.logger.info(\"DryRun: Would update MetadataProfiles.\");\n      return {\n        added: diff.missingOnServer.length,\n        updated: diff.changed.length,\n      };\n    }\n\n    let added = 0,\n      updated = 0;\n\n    // Add missing profiles\n    for (const profile of diff.missingOnServer) {\n      this.logger.info(`Adding MetadataProfile missing on server: ${profile.name}`);\n      const resolvedConfig = await this.resolveConfig(profile, serverCache);\n      await this.createMetadataProfile(resolvedConfig);\n      added++;\n    }\n\n    // Update changed profiles\n    for (const { config, server } of diff.changed) {\n      this.logger.info(`Updating MetadataProfile: ${config.name}`);\n      const resolvedConfig = await this.resolveConfig(config, serverCache);\n      await this.updateMetadataProfile(String(server.id), resolvedConfig);\n      updated++;\n    }\n\n    return { added, updated };\n  }\n\n  private async performDeletion(\n    managedProfiles: InputConfigMetadataProfile[],\n    deleteConfig: NonNullable<MergedConfigInstance[\"delete_unmanaged_metadata_profiles\"]>,\n  ): Promise<number> {\n    const shouldDelete = deleteConfig.enabled;\n\n    if (!shouldDelete) {\n      return 0;\n    }\n\n    const ignoreList = deleteConfig.ignore ?? [];\n    const serverProfiles = await this.loadFromServer();\n    const managedNames = new Set(managedProfiles.map((p) => p.name));\n    const ignoreSet = new Set(ignoreList);\n\n    // Always ignore the built-in 'None' metadata profile by default (e.g. Readarr, Lidarr).\n    ignoreSet.add(\"None\");\n\n    const toDelete = serverProfiles.filter((p: any) => p.name && !managedNames.has(p.name) && !ignoreSet.has(p.name));\n\n    if (toDelete.length === 0) {\n      return 0;\n    }\n\n    if (getEnvs().DRY_RUN) {\n      this.logger.info(\n        `DryRun: Would delete ${toDelete.length} unmanaged MetadataProfiles: ${toDelete.map((p: any) => p.name).join(\", \")}`,\n      );\n      return toDelete.length;\n    }\n\n    this.logger.info(`Deleting ${toDelete.length} unmanaged MetadataProfiles ...`);\n    let deleted = 0;\n\n    for (const profile of toDelete) {\n      try {\n        await this.deleteProfile(String(profile.id));\n        this.logger.info(`Deleted MetadataProfile: '${profile.name || profile.id}'`);\n        deleted++;\n      } catch (err: any) {\n        // Check if profile is in use\n        const errorMessage = err?.message || err?.toString() || \"\";\n        const isInUse = errorMessage.toLowerCase().includes(\"in use\") || errorMessage.toLowerCase().includes(\"being used\");\n\n        if (isInUse) {\n          this.logger.info(`Metadata profile \"${profile.name ?? profile.id}\" is in use and could not be deleted.`);\n        } else {\n          this.logger.error(\n            `Failed deleting MetadataProfile (${profile.name ?? profile.id}). ` +\n              \"This profile will be left in place; check your Arr logs if you expected it to be removable.\",\n          );\n          this.logger.debug(err, \"Error while deleting MetadataProfile\");\n        }\n        // Continue with other profiles; deleting unmanaged metadata profiles is best-effort.\n      }\n    }\n\n    return deleted;\n  }\n}\n"
  },
  {
    "path": "src/metadataProfiles/metadataProfileLidarr.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { LidarrMetadataProfileSync } from \"./metadataProfileLidarr\";\nimport { ServerCache } from \"../cache\";\nimport { InputConfigLidarrMetadataProfile } from \"../types/config.types\";\nimport { getUnifiedClient, getSpecificClient } from \"../clients/unified-client\";\n\n// Mock the unified client\nvi.mock(\"../clients/unified-client\", () => ({\n  getUnifiedClient: vi.fn(),\n  getSpecificClient: vi.fn(),\n}));\n\ndescribe(\"LidarrMetadataProfileSync\", () => {\n  const mockApi = {\n    getMetadataProfiles: vi.fn(),\n    createMetadataProfile: vi.fn(),\n    updateMetadataProfile: vi.fn(),\n    deleteMetadataProfile: vi.fn(),\n  };\n\n  let serverCache: ServerCache;\n\n  beforeEach(() => {\n    vi.clearAllMocks();\n    mockApi.getMetadataProfiles.mockResolvedValue([]);\n    (getUnifiedClient as any).mockReturnValue({\n      api: mockApi,\n    });\n    (getSpecificClient as any).mockReturnValue(mockApi);\n    serverCache = new ServerCache([], [], [], []);\n  });\n\n  describe(\"resolveConfig\", () => {\n    it(\"should resolve basic Lidarr config\", async () => {\n      const sync = new LidarrMetadataProfileSync();\n      const config: InputConfigLidarrMetadataProfile = {\n        name: \"Test Profile\",\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n      });\n    });\n\n    it(\"should resolve config with primary types - new profile\", async () => {\n      const sync = new LidarrMetadataProfileSync();\n      const config: InputConfigLidarrMetadataProfile = {\n        name: \"Test Profile\",\n        primary_types: [\"Album\", \"EP\"],\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n        id: undefined,\n        primaryAlbumTypes: [\n          { albumType: \"Album\", allowed: true },\n          { albumType: \"EP\", allowed: true },\n        ],\n      });\n    });\n\n    it(\"should resolve config with primary types - updating existing profile\", async () => {\n      const serverProfile = {\n        id: 1,\n        name: \"Test Profile\",\n        primaryAlbumTypes: [\n          { id: 1, albumType: { id: 1, name: \"Album\" }, allowed: true },\n          { id: 2, albumType: { id: 2, name: \"EP\" }, allowed: true },\n          { id: 3, albumType: { id: 3, name: \"Single\" }, allowed: false },\n        ],\n      };\n      mockApi.getMetadataProfiles.mockResolvedValue([serverProfile]);\n\n      const sync = new LidarrMetadataProfileSync();\n      const config: InputConfigLidarrMetadataProfile = {\n        name: \"Test Profile\",\n        primary_types: [\"Album\", \"EP\"], // Single not listed, should be disabled\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n        id: 1,\n        primaryAlbumTypes: [\n          { id: 1, albumType: { id: 1, name: \"Album\" }, allowed: true },\n          { id: 2, albumType: { id: 2, name: \"EP\" }, allowed: true },\n          { id: 3, albumType: { id: 3, name: \"Single\" }, allowed: false }, // Disabled\n        ],\n      });\n    });\n\n    it(\"should resolve config with secondary types\", async () => {\n      const sync = new LidarrMetadataProfileSync();\n      const config: InputConfigLidarrMetadataProfile = {\n        name: \"Test Profile\",\n        secondary_types: [\"Compilation\", \"Live\"],\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n        id: undefined,\n        secondaryAlbumTypes: [\n          { albumType: \"Compilation\", allowed: true },\n          { albumType: \"Live\", allowed: true },\n        ],\n      });\n    });\n\n    it(\"should resolve config with release statuses\", async () => {\n      const sync = new LidarrMetadataProfileSync();\n      const config: InputConfigLidarrMetadataProfile = {\n        name: \"Test Profile\",\n        release_statuses: [\"Official\", \"Promotion\"],\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n        id: undefined,\n        releaseStatuses: [\n          { releaseStatus: \"Official\", allowed: true },\n          { releaseStatus: \"Promotion\", allowed: true },\n        ],\n      });\n    });\n\n    it(\"should disable types not in the config array when updating\", async () => {\n      const serverProfile = {\n        id: 1,\n        name: \"Test Profile\",\n        secondaryAlbumTypes: [\n          { id: 1, albumType: { id: 1, name: \"Studio\" }, allowed: true },\n          { id: 2, albumType: { id: 2, name: \"Live\" }, allowed: true },\n          { id: 3, albumType: { id: 3, name: \"Compilation\" }, allowed: true },\n        ],\n      };\n      mockApi.getMetadataProfiles.mockResolvedValue([serverProfile]);\n\n      const sync = new LidarrMetadataProfileSync();\n      const config: InputConfigLidarrMetadataProfile = {\n        name: \"Test Profile\",\n        secondary_types: [\"Studio\"], // Only Studio enabled, others should be disabled\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n        id: 1,\n        secondaryAlbumTypes: [\n          { id: 1, albumType: { id: 1, name: \"Studio\" }, allowed: true },\n          { id: 2, albumType: { id: 2, name: \"Live\" }, allowed: false }, // Disabled\n          { id: 3, albumType: { id: 3, name: \"Compilation\" }, allowed: false }, // Disabled\n        ],\n      });\n    });\n  });\n\n  describe(\"validation\", () => {\n    it(\"should throw error for empty primary_types array in calculateDiff\", async () => {\n      const sync = new LidarrMetadataProfileSync();\n      const config: InputConfigLidarrMetadataProfile = {\n        name: \"Test Profile\",\n        primary_types: [], // Empty array should be rejected\n        secondary_types: [\"Studio\"],\n        release_statuses: [\"Official\"],\n      };\n\n      await expect(sync.calculateDiff([config], serverCache)).rejects.toThrow(\"primary_types must be defined\");\n    });\n\n    it(\"should throw error for empty secondary_types array in calculateDiff\", async () => {\n      const sync = new LidarrMetadataProfileSync();\n      const config: InputConfigLidarrMetadataProfile = {\n        name: \"Test Profile\",\n        primary_types: [\"Album\"],\n        secondary_types: [], // Empty array should be rejected\n        release_statuses: [\"Official\"],\n      };\n\n      await expect(sync.calculateDiff([config], serverCache)).rejects.toThrow(\"secondary_types must be defined\");\n    });\n\n    it(\"should throw error for empty release_statuses array in calculateDiff\", async () => {\n      const sync = new LidarrMetadataProfileSync();\n      const config: InputConfigLidarrMetadataProfile = {\n        name: \"Test Profile\",\n        primary_types: [\"Album\"],\n        secondary_types: [\"Studio\"],\n        release_statuses: [], // Empty array should be rejected\n      };\n\n      await expect(sync.calculateDiff([config], serverCache)).rejects.toThrow(\"release_statuses must be defined\");\n    });\n\n    it(\"should validate all profiles and report all errors at once\", async () => {\n      const sync = new LidarrMetadataProfileSync();\n      const configs: InputConfigLidarrMetadataProfile[] = [\n        {\n          name: \"Profile 1\",\n          primary_types: [], // Error\n          secondary_types: [\"Studio\"],\n          release_statuses: [\"Official\"],\n        },\n        {\n          name: \"Profile 2\",\n          primary_types: [\"Album\"],\n          secondary_types: [], // Error\n          release_statuses: [\"Official\"],\n        },\n        {\n          name: \"Profile 3\",\n          primary_types: [\"Album\"],\n          secondary_types: [\"Studio\"],\n          release_statuses: [], // Error\n        },\n      ];\n\n      await expect(sync.calculateDiff(configs, serverCache)).rejects.toThrow(\"Metadata profile validation failed\");\n    });\n  });\n\n  describe(\"calculateDiff\", () => {\n    it(\"should detect missing profiles\", async () => {\n      mockApi.getMetadataProfiles.mockResolvedValue([]);\n\n      const sync = new LidarrMetadataProfileSync();\n      const result = await sync.calculateDiff(\n        [{ name: \"New Profile\", primary_types: [\"Album\"], secondary_types: [\"Studio\"], release_statuses: [\"Official\"] }],\n        serverCache,\n      );\n\n      expect(result).toEqual({\n        missingOnServer: [{ name: \"New Profile\", primary_types: [\"Album\"], secondary_types: [\"Studio\"], release_statuses: [\"Official\"] }],\n        noChanges: [],\n        changed: [],\n      });\n    });\n\n    it(\"should return null for empty config (no automatic deletion)\", async () => {\n      mockApi.getMetadataProfiles.mockResolvedValue([{ name: \"Old Profile\", id: 1 }]);\n\n      const sync = new LidarrMetadataProfileSync();\n      const result = await sync.calculateDiff([], serverCache);\n\n      // Empty config means no profiles to manage, so no changes\n      expect(result).toBeNull();\n    });\n\n    it(\"should ignore unmanaged profiles (no automatic deletion)\", async () => {\n      mockApi.getMetadataProfiles.mockResolvedValue([{ name: \"Server Profile\", id: 1 }]);\n\n      const sync = new LidarrMetadataProfileSync();\n      const result = await sync.calculateDiff([], serverCache);\n\n      // Unmanaged profiles are not included in diff - deletion is handled separately\n      expect(result).toBeNull();\n    });\n\n    it(\"should return null when config is null\", async () => {\n      const sync = new LidarrMetadataProfileSync();\n      const result = await sync.calculateDiff(null as any, serverCache);\n\n      expect(result).toBeNull();\n    });\n\n    it(\"should detect no changes when profiles match\", async () => {\n      const serverProfile = {\n        name: \"Test Profile\",\n        id: 1,\n        primaryAlbumTypes: [\n          { id: 1, albumType: { id: 1, name: \"Album\" }, allowed: true },\n          { id: 2, albumType: { id: 2, name: \"EP\" }, allowed: false },\n        ],\n        secondaryAlbumTypes: [{ id: 1, albumType: { id: 1, name: \"Studio\" }, allowed: true }],\n        releaseStatuses: [{ id: 1, releaseStatus: { id: 1, name: \"Official\" }, allowed: true }],\n      };\n      mockApi.getMetadataProfiles.mockResolvedValue([serverProfile]);\n\n      const sync = new LidarrMetadataProfileSync();\n      const config: InputConfigLidarrMetadataProfile = {\n        name: \"Test Profile\",\n        primary_types: [\"Album\"], // Only Album enabled\n        secondary_types: [\"Studio\"],\n        release_statuses: [\"Official\"],\n      };\n\n      const result = await sync.calculateDiff([config], serverCache);\n\n      expect(result).toBeNull();\n    });\n\n    it(\"should detect changes when enabled types differ\", async () => {\n      const serverProfile = {\n        name: \"Test Profile\",\n        id: 1,\n        primaryAlbumTypes: [\n          { id: 1, albumType: { id: 1, name: \"Album\" }, allowed: true },\n          { id: 2, albumType: { id: 2, name: \"EP\" }, allowed: false },\n        ],\n        secondaryAlbumTypes: [{ id: 1, albumType: { id: 1, name: \"Studio\" }, allowed: true }],\n        releaseStatuses: [{ id: 1, releaseStatus: { id: 1, name: \"Official\" }, allowed: true }],\n      };\n      mockApi.getMetadataProfiles.mockResolvedValue([serverProfile]);\n\n      const sync = new LidarrMetadataProfileSync();\n      const config: InputConfigLidarrMetadataProfile = {\n        name: \"Test Profile\",\n        primary_types: [\"Album\", \"EP\"], // EP should be enabled but is disabled on server\n        secondary_types: [\"Studio\"],\n        release_statuses: [\"Official\"],\n      };\n\n      const result = await sync.calculateDiff([config], serverCache);\n\n      expect(result).toEqual({\n        missingOnServer: [],\n        noChanges: [],\n        changed: [{ config, server: serverProfile }],\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/metadataProfiles/metadataProfileLidarr.ts",
    "content": "import { MetadataProfileResource, PrimaryAlbumType, ReleaseStatus, SecondaryAlbumType } from \"../__generated__/lidarr/data-contracts\";\nimport { ServerCache } from \"../cache\";\nimport { LidarrClient } from \"../clients/lidarr-client\";\nimport { getSpecificClient } from \"../clients/unified-client\";\nimport { InputConfigLidarrMetadataProfile, InputConfigMetadataProfile } from \"../types/config.types\";\nimport { MetadataProfileDiff } from \"./metadataProfile.types\";\nimport { BaseMetadataProfileSync } from \"./metadataProfileBase\";\n\nexport class LidarrMetadataProfileSync extends BaseMetadataProfileSync<MetadataProfileResource> {\n  protected api: LidarrClient = getSpecificClient(\"LIDARR\");\n\n  protected getArrType(): \"LIDARR\" {\n    return \"LIDARR\";\n  }\n\n  protected createMetadataProfile(resolvedConfig: MetadataProfileResource): Promise<MetadataProfileResource> {\n    return this.api.createMetadataProfile(resolvedConfig);\n  }\n\n  protected updateMetadataProfile(id: string, resolvedConfig: MetadataProfileResource): Promise<MetadataProfileResource> {\n    return this.api.updateMetadataProfile(id, resolvedConfig);\n  }\n\n  protected deleteProfile(id: string): Promise<void> {\n    return this.api.deleteMetadataProfile(id);\n  }\n\n  protected async loadFromServer(): Promise<MetadataProfileResource[]> {\n    return await this.api.getMetadataProfiles();\n  }\n\n  private validateProfile(config: InputConfigLidarrMetadataProfile): void {\n    const errors: string[] = [];\n\n    // primary_types: must be defined AND not empty\n    if (!config.primary_types || config.primary_types.length === 0) {\n      errors.push(`primary_types must be defined with at least one type.`);\n    }\n\n    // secondary_types: must be defined AND not empty\n    if (!config.secondary_types || config.secondary_types.length === 0) {\n      errors.push(`secondary_types must be defined with at least one type.`);\n    }\n\n    // release_statuses: must be defined AND not empty\n    if (!config.release_statuses || config.release_statuses.length === 0) {\n      errors.push(`release_statuses must be defined with at least one status.`);\n    }\n\n    if (errors.length > 0) {\n      throw new Error(`Metadata profile '${config.name}':\\n  - ${errors.join(\"\\n  - \")}`);\n    }\n  }\n\n  private validateAllProfiles(profiles: InputConfigMetadataProfile[]): void {\n    const allErrors: string[] = [];\n\n    for (const profile of profiles) {\n      try {\n        this.validateProfile(profile as InputConfigLidarrMetadataProfile);\n      } catch (error) {\n        allErrors.push(error instanceof Error ? error.message : String(error));\n      }\n    }\n\n    if (allErrors.length > 0) {\n      throw new Error(`Metadata profile validation failed:\\n\\n${allErrors.join(\"\\n\\n\")}`);\n    }\n  }\n\n  public async resolveConfig(config: InputConfigMetadataProfile, serverCache: ServerCache): Promise<MetadataProfileResource> {\n    const lidarrConfig = config as InputConfigLidarrMetadataProfile;\n\n    const serverProfiles = await this.loadFromServer();\n    const existingProfile = serverProfiles.find((p) => p.name === lidarrConfig.name);\n\n    const result: MetadataProfileResource = {\n      name: lidarrConfig.name,\n      id: existingProfile?.id,\n    };\n\n    // When creating a new profile, we need to get ALL available types from the server schema\n    // This ensures we have the complete structure with proper IDs\n    let schemaTemplate: MetadataProfileResource | undefined;\n    if (!existingProfile) {\n      try {\n        schemaTemplate = await this.api.getMetadataProfileSchema!();\n        this.logger.debug(`Fetched schema for new profile '${lidarrConfig.name}'`);\n      } catch (error) {\n        this.logger.warn(`Failed to fetch schema for new profile, will try simple structure: ${error}`);\n      }\n    }\n\n    // Map primary album types\n    if (lidarrConfig.primary_types) {\n      if (existingProfile?.primaryAlbumTypes) {\n        // Updating - merge with server data to preserve structure\n        // Only types in the config array should be enabled, all others disabled\n        const enabledTypes = new Set(lidarrConfig.primary_types);\n        result.primaryAlbumTypes = existingProfile.primaryAlbumTypes.map((serverItem) => ({\n          ...serverItem,\n          allowed: enabledTypes.has(serverItem.albumType?.name as string),\n        }));\n      } else if (schemaTemplate?.primaryAlbumTypes) {\n        // Creating new - use schema template with ALL types, enable only config types\n        const enabledTypes = new Set(lidarrConfig.primary_types);\n        result.primaryAlbumTypes = schemaTemplate.primaryAlbumTypes.map((serverItem) => ({\n          ...serverItem,\n          allowed: enabledTypes.has(serverItem.albumType?.name as string),\n        }));\n      } else {\n        // No schema available - use simple structure (might fail)\n        result.primaryAlbumTypes = lidarrConfig.primary_types.map((typeName) => ({\n          albumType: typeName as PrimaryAlbumType,\n          allowed: true,\n        }));\n      }\n    }\n\n    // Map secondary album types\n    if (lidarrConfig.secondary_types) {\n      if (existingProfile?.secondaryAlbumTypes) {\n        const enabledTypes = new Set(lidarrConfig.secondary_types);\n        result.secondaryAlbumTypes = existingProfile.secondaryAlbumTypes.map((serverItem) => ({\n          ...serverItem,\n          allowed: enabledTypes.has(serverItem.albumType?.name as string),\n        }));\n      } else if (schemaTemplate?.secondaryAlbumTypes) {\n        // Creating new - use schema template with ALL types\n        const enabledTypes = new Set(lidarrConfig.secondary_types);\n        result.secondaryAlbumTypes = schemaTemplate.secondaryAlbumTypes.map((serverItem) => ({\n          ...serverItem,\n          allowed: enabledTypes.has(serverItem.albumType?.name as string),\n        }));\n      } else {\n        result.secondaryAlbumTypes = lidarrConfig.secondary_types.map((typeName) => ({\n          albumType: typeName as SecondaryAlbumType,\n          allowed: true,\n        }));\n      }\n    }\n\n    // Map release statuses\n    if (lidarrConfig.release_statuses) {\n      if (existingProfile?.releaseStatuses) {\n        const enabledStatuses = new Set(lidarrConfig.release_statuses);\n        result.releaseStatuses = existingProfile.releaseStatuses.map((serverItem) => ({\n          ...serverItem,\n          allowed: enabledStatuses.has(serverItem.releaseStatus?.name as string),\n        }));\n      } else if (schemaTemplate?.releaseStatuses) {\n        // Creating new - use schema template with ALL statuses\n        const enabledStatuses = new Set(lidarrConfig.release_statuses);\n        result.releaseStatuses = schemaTemplate.releaseStatuses.map((serverItem) => ({\n          ...serverItem,\n          allowed: enabledStatuses.has(serverItem.releaseStatus?.name as string),\n        }));\n      } else {\n        result.releaseStatuses = lidarrConfig.release_statuses.map((statusName) => ({\n          releaseStatus: statusName as ReleaseStatus,\n          allowed: true,\n        }));\n      }\n    }\n\n    return result;\n  }\n\n  private isConfigEqual(\n    resolvedConfig: MetadataProfileResource,\n    serverProfile: MetadataProfileResource,\n    originalConfig: InputConfigLidarrMetadataProfile,\n  ): boolean {\n    // Compare name and allowed states\n    // Only compare fields that are actually defined in the original config\n    // If a field is undefined in config, we skip checking it (leave server as-is)\n\n    const normalizeForComparison = (profile: MetadataProfileResource) => {\n      const extractName = (item: any): string => {\n        // Handle both nested {albumType: {name: \"X\"}} and flat {albumType: \"X\"}\n        if (typeof item === \"string\") return item;\n        if (item?.name) return item.name;\n        return \"\";\n      };\n\n      return {\n        name: profile.name ?? \"\",\n        primaryAlbumTypes:\n          profile.primaryAlbumTypes\n            ?.map((item) => ({\n              name: extractName(item.albumType),\n              allowed: item.allowed ?? false,\n            }))\n            .sort((a, b) => a.name.localeCompare(b.name)) ?? [],\n        secondaryAlbumTypes:\n          profile.secondaryAlbumTypes\n            ?.map((item) => ({\n              name: extractName(item.albumType),\n              allowed: item.allowed ?? false,\n            }))\n            .sort((a, b) => a.name.localeCompare(b.name)) ?? [],\n        releaseStatuses:\n          profile.releaseStatuses\n            ?.map((item) => ({\n              name: extractName(item.releaseStatus),\n              allowed: item.allowed ?? false,\n            }))\n            .sort((a, b) => a.name.localeCompare(b.name)) ?? [],\n      };\n    };\n\n    const normalizedConfig = normalizeForComparison(resolvedConfig);\n    const normalizedServer = normalizeForComparison(serverProfile);\n\n    // Build sets of enabled types from config (only for defined fields)\n    const configEnabledPrimary = new Set(normalizedConfig.primaryAlbumTypes.filter((t) => t.allowed).map((t) => t.name));\n    const configEnabledSecondary = new Set(normalizedConfig.secondaryAlbumTypes.filter((t) => t.allowed).map((t) => t.name));\n    const configEnabledStatuses = new Set(normalizedConfig.releaseStatuses.filter((t) => t.allowed).map((t) => t.name));\n\n    // Check primary types - ONLY if defined in config\n    if (originalConfig.primary_types !== undefined) {\n      for (const serverType of normalizedServer.primaryAlbumTypes) {\n        const shouldBeEnabled = configEnabledPrimary.has(serverType.name);\n        if (serverType.allowed !== shouldBeEnabled) {\n          return false;\n        }\n      }\n    }\n\n    // Check secondary types - ONLY if defined in config\n    if (originalConfig.secondary_types !== undefined) {\n      for (const serverType of normalizedServer.secondaryAlbumTypes) {\n        const shouldBeEnabled = configEnabledSecondary.has(serverType.name);\n        if (serverType.allowed !== shouldBeEnabled) {\n          return false;\n        }\n      }\n    }\n\n    // Check release statuses - ONLY if defined in config\n    if (originalConfig.release_statuses !== undefined) {\n      for (const serverStatus of normalizedServer.releaseStatuses) {\n        const shouldBeEnabled = configEnabledStatuses.has(serverStatus.name);\n        if (serverStatus.allowed !== shouldBeEnabled) {\n          return false;\n        }\n      }\n    }\n\n    return true;\n  }\n\n  async calculateDiff(\n    profiles: InputConfigMetadataProfile[],\n    serverCache: ServerCache,\n  ): Promise<MetadataProfileDiff<MetadataProfileResource> | null> {\n    if (profiles == null) {\n      this.logger.debug(`Config 'metadata_profiles' not specified. Ignoring.`);\n      return null;\n    }\n\n    // Validate ALL profiles FIRST before any processing\n    this.validateAllProfiles(profiles);\n\n    const serverData = await this.loadFromServer();\n\n    const missingOnServer: InputConfigMetadataProfile[] = [];\n    const changed: Array<{ config: InputConfigMetadataProfile; server: MetadataProfileResource }> = [];\n    const noChanges: MetadataProfileResource[] = [];\n\n    // Create maps for efficient lookup\n    const serverByName = new Map<string, MetadataProfileResource>();\n    serverData.forEach((profile) => {\n      if (profile.name) {\n        serverByName.set(profile.name, profile);\n      }\n    });\n\n    // Process each config profile\n    for (const configProfile of profiles) {\n      const serverProfile = serverByName.get(configProfile.name);\n\n      if (!serverProfile) {\n        // Profile doesn't exist on server\n        missingOnServer.push(configProfile);\n      } else {\n        // Profile exists, check if configuration matches\n        // For comparison, create resolved config WITHOUT server data (simple structure)\n        const lidarrConfig = configProfile as InputConfigLidarrMetadataProfile;\n        const simpleResolvedConfig: MetadataProfileResource = {\n          name: lidarrConfig.name,\n          primaryAlbumTypes: lidarrConfig.primary_types?.map((typeName) => ({\n            albumType: typeName as PrimaryAlbumType,\n            allowed: true,\n          })),\n          secondaryAlbumTypes: lidarrConfig.secondary_types?.map((typeName) => ({\n            albumType: typeName as SecondaryAlbumType,\n            allowed: true,\n          })),\n          releaseStatuses: lidarrConfig.release_statuses?.map((statusName) => ({\n            releaseStatus: statusName as ReleaseStatus,\n            allowed: true,\n          })),\n        };\n\n        const isChanged = !this.isConfigEqual(simpleResolvedConfig, serverProfile, lidarrConfig);\n        if (isChanged) {\n          changed.push({ config: configProfile, server: serverProfile });\n        } else {\n          noChanges.push(serverProfile);\n        }\n        // Remove from serverByName so we know it was managed\n        serverByName.delete(configProfile.name);\n      }\n    }\n\n    this.logger.debug({ missingOnServer, changed, noChanges }, \"Metadata profile comparison\");\n\n    if (missingOnServer.length === 0 && changed.length === 0) {\n      this.logger.debug(`Metadata profiles are in sync`);\n      return null;\n    }\n\n    this.logger.info(`Found ${missingOnServer.length + changed.length} differences for metadata profiles.`);\n\n    return {\n      missingOnServer,\n      changed,\n      noChanges,\n    };\n  }\n}\n"
  },
  {
    "path": "src/metadataProfiles/metadataProfileReadarr.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { ReadarrMetadataProfileSync } from \"./metadataProfileReadarr\";\nimport { ServerCache } from \"../cache\";\nimport { InputConfigReadarrMetadataProfile } from \"../types/config.types\";\nimport { getUnifiedClient, getSpecificClient } from \"../clients/unified-client\";\n\n// Mock the unified client\nvi.mock(\"../clients/unified-client\", () => ({\n  getUnifiedClient: vi.fn(),\n  getSpecificClient: vi.fn(),\n}));\n\ndescribe(\"ReadarrMetadataProfileSync\", () => {\n  const mockApi = {\n    getMetadataProfiles: vi.fn(),\n    createMetadataProfile: vi.fn(),\n    updateMetadataProfile: vi.fn(),\n    deleteMetadataProfile: vi.fn(),\n  };\n\n  let serverCache: ServerCache;\n\n  beforeEach(() => {\n    vi.clearAllMocks();\n    (getUnifiedClient as any).mockReturnValue({\n      api: mockApi,\n    });\n    (getSpecificClient as any).mockReturnValue(mockApi);\n    serverCache = new ServerCache([], [], [], []);\n  });\n\n  describe(\"resolveConfig\", () => {\n    it(\"should resolve basic Readarr config\", async () => {\n      const sync = new ReadarrMetadataProfileSync();\n      const config: InputConfigReadarrMetadataProfile = {\n        name: \"Test Profile\",\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n      });\n    });\n\n    it(\"should resolve config with snake_case fields\", async () => {\n      const sync = new ReadarrMetadataProfileSync();\n      const config: InputConfigReadarrMetadataProfile = {\n        name: \"Test Profile\",\n        min_popularity: 50,\n        skip_missing_date: true,\n        skip_missing_isbn: false,\n        skip_parts_and_sets: true,\n        skip_secondary_series: false,\n        allowed_languages: [\"eng\", \"fra\", \"deu\"],\n        min_pages: 10,\n        must_not_contain: [\"bad\", \"words\"],\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n        minPopularity: 50,\n        skipMissingDate: true,\n        skipMissingIsbn: false,\n        skipPartsAndSets: true,\n        skipSeriesSecondary: false,\n        allowedLanguages: \"eng,fra,deu\",\n        minPages: 10,\n        ignored: [\"bad\", \"words\"],\n      });\n    });\n\n    it(\"should normalize allowed languages array\", async () => {\n      const sync = new ReadarrMetadataProfileSync();\n      const config: InputConfigReadarrMetadataProfile = {\n        name: \"Test Profile\",\n        allowed_languages: [\"eng\", \"fra\", \"deu\"],\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n        allowedLanguages: \"eng,fra,deu\",\n      });\n    });\n\n    it(\"should normalize allowed languages array with multiple formats\", async () => {\n      const sync = new ReadarrMetadataProfileSync();\n      const config: InputConfigReadarrMetadataProfile = {\n        name: \"Test Profile\",\n        allowed_languages: [\"eng\", \"fra\", \"deu\", \"spa\"],\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n        allowedLanguages: \"eng,fra,deu,spa\",\n      });\n    });\n\n    it(\"should handle null/undefined allowed languages\", async () => {\n      const sync = new ReadarrMetadataProfileSync();\n      const config: InputConfigReadarrMetadataProfile = {\n        name: \"Test Profile\",\n        allowed_languages: null,\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n      });\n    });\n\n    it(\"should normalize ignored field to array\", async () => {\n      const sync = new ReadarrMetadataProfileSync();\n      const config: InputConfigReadarrMetadataProfile = {\n        name: \"Test Profile\",\n        must_not_contain: [\"single,word\"],\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n        ignored: [\"single,word\"],\n      });\n    });\n\n    it(\"should handle ignored as array\", async () => {\n      const sync = new ReadarrMetadataProfileSync();\n      const config: InputConfigReadarrMetadataProfile = {\n        name: \"Test Profile\",\n        must_not_contain: [\"word1\", \"word2\"],\n      };\n\n      const result = await sync.resolveConfig(config, serverCache);\n\n      expect(result).toEqual({\n        name: \"Test Profile\",\n        ignored: [\"word1\", \"word2\"],\n      });\n    });\n  });\n\n  describe(\"calculateDiff\", () => {\n    it(\"should detect missing profiles\", async () => {\n      mockApi.getMetadataProfiles.mockResolvedValue([]);\n\n      const sync = new ReadarrMetadataProfileSync();\n      const result = await sync.calculateDiff([{ name: \"New Profile\" }], serverCache);\n\n      expect(result).toEqual({\n        missingOnServer: [{ name: \"New Profile\" }],\n        noChanges: [],\n        changed: [],\n      });\n    });\n\n    it(\"should return null when config is empty array\", async () => {\n      mockApi.getMetadataProfiles.mockResolvedValue([{ name: \"Old Profile\", id: 1 }]);\n\n      const sync = new ReadarrMetadataProfileSync();\n      const result = await sync.calculateDiff([], serverCache);\n\n      // Empty config means no management - return null instead of deleting everything\n      expect(result).toBeNull();\n    });\n\n    it(\"should return null when config is null\", async () => {\n      const sync = new ReadarrMetadataProfileSync();\n      const result = await sync.calculateDiff(null as any, serverCache);\n\n      expect(result).toBeNull();\n    });\n\n    it(\"should detect configuration changes\", async () => {\n      const serverProfile = {\n        name: \"Test Profile\",\n        id: 1,\n        minPopularity: 50,\n        skipMissingDate: false,\n      };\n      mockApi.getMetadataProfiles.mockResolvedValue([serverProfile]);\n\n      const sync = new ReadarrMetadataProfileSync();\n      const config: InputConfigReadarrMetadataProfile = {\n        name: \"Test Profile\",\n        min_popularity: 75, // Different from server\n        skip_missing_date: true, // Different from server\n      };\n\n      const result = await sync.calculateDiff([config], serverCache);\n\n      expect(result).toEqual({\n        missingOnServer: [],\n        noChanges: [],\n        changed: [{ config, server: serverProfile }],\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/metadataProfiles/metadataProfileReadarr.ts",
    "content": "import { MetadataProfileResource } from \"../__generated__/readarr/data-contracts\";\nimport { ServerCache } from \"../cache\";\nimport { ReadarrClient } from \"../clients/readarr-client\";\nimport { getSpecificClient } from \"../clients/unified-client\";\nimport { InputConfigReadarrMetadataProfile, InputConfigMetadataProfile } from \"../types/config.types\";\nimport { compareObjectsCarr } from \"../util\";\nimport { MetadataProfileDiff } from \"./metadataProfile.types\";\nimport { BaseMetadataProfileSync } from \"./metadataProfileBase\";\n\nexport class ReadarrMetadataProfileSync extends BaseMetadataProfileSync<MetadataProfileResource> {\n  protected api: ReadarrClient = getSpecificClient(\"READARR\");\n\n  protected getArrType(): \"READARR\" {\n    return \"READARR\";\n  }\n\n  protected createMetadataProfile(resolvedConfig: MetadataProfileResource): Promise<MetadataProfileResource> {\n    return this.api.createMetadataProfile(resolvedConfig);\n  }\n\n  protected updateMetadataProfile(id: string, resolvedConfig: MetadataProfileResource): Promise<MetadataProfileResource> {\n    return this.api.updateMetadataProfile(id, resolvedConfig);\n  }\n\n  protected deleteProfile(id: string): Promise<void> {\n    return this.api.deleteMetadataProfile(id);\n  }\n\n  private normalizeReadarrAllowedLanguages(value: string | string[] | null | undefined): string | null {\n    if (value == null) {\n      return null;\n    }\n\n    const rawParts: string[] = [];\n\n    if (Array.isArray(value)) {\n      for (const entry of value) {\n        if (entry === null || entry === undefined) {\n          rawParts.push(\"null\");\n        } else {\n          rawParts.push(String(entry));\n        }\n      }\n    } else {\n      rawParts.push(\n        ...value\n          .split(/[;, ]+/)\n          .map((p) => p.trim())\n          .filter((p) => p.length > 0),\n      );\n    }\n\n    const parts = rawParts.map((p) => p.trim()).filter((p) => p.length > 0);\n\n    if (!parts.length) {\n      return null;\n    }\n\n    // Deduplicate while preserving order\n    const unique: string[] = [];\n    for (const code of parts) {\n      if (!unique.includes(code)) {\n        unique.push(code);\n      }\n    }\n\n    return unique.join(\",\");\n  }\n\n  protected async loadFromServer(): Promise<MetadataProfileResource[]> {\n    return await this.api.getMetadataProfiles();\n  }\n\n  public async resolveConfig(config: InputConfigMetadataProfile, serverCache: ServerCache): Promise<MetadataProfileResource> {\n    const readarrConfig = config as InputConfigReadarrMetadataProfile;\n\n    const result: MetadataProfileResource = {\n      name: readarrConfig.name,\n    };\n\n    if (readarrConfig.min_popularity !== undefined) {\n      result.minPopularity = readarrConfig.min_popularity;\n    }\n\n    if (readarrConfig.skip_missing_date !== undefined) {\n      result.skipMissingDate = readarrConfig.skip_missing_date;\n    }\n\n    if (readarrConfig.skip_missing_isbn !== undefined) {\n      result.skipMissingIsbn = readarrConfig.skip_missing_isbn;\n    }\n\n    if (readarrConfig.skip_parts_and_sets !== undefined) {\n      result.skipPartsAndSets = readarrConfig.skip_parts_and_sets;\n    }\n\n    if (readarrConfig.skip_secondary_series !== undefined) {\n      result.skipSeriesSecondary = readarrConfig.skip_secondary_series;\n    }\n\n    // Normalize allowed languages\n    const allowedLanguages = this.normalizeReadarrAllowedLanguages(readarrConfig.allowed_languages ?? null);\n    if (allowedLanguages !== null) {\n      result.allowedLanguages = allowedLanguages;\n    }\n\n    // Normalize minimum pages\n    if (readarrConfig.min_pages !== undefined && readarrConfig.min_pages !== null) {\n      result.minPages = readarrConfig.min_pages;\n    }\n\n    // Normalize ignored terms\n    if (readarrConfig.must_not_contain !== undefined) {\n      const ignoredArray = Array.isArray(readarrConfig.must_not_contain)\n        ? readarrConfig.must_not_contain\n        : [readarrConfig.must_not_contain];\n      result.ignored = ignoredArray;\n    }\n\n    return result;\n  }\n\n  private isConfigEqual(resolvedConfig: MetadataProfileResource, serverProfile: MetadataProfileResource): boolean {\n    // Normalize both for comparison\n    const normalizeForComparison = (profile: MetadataProfileResource) => {\n      const rawIgnored = profile.ignored ?? [];\n      const ignoredArray = Array.isArray(rawIgnored) ? rawIgnored : [String(rawIgnored)];\n\n      const normalizedAllowed = this.normalizeReadarrAllowedLanguages(profile.allowedLanguages ?? null);\n\n      return {\n        name: profile.name ?? \"\",\n        minPopularity: profile.minPopularity ?? 0,\n        skipMissingDate: Boolean(profile.skipMissingDate),\n        skipMissingIsbn: Boolean(profile.skipMissingIsbn),\n        skipPartsAndSets: Boolean(profile.skipPartsAndSets),\n        skipSeriesSecondary: Boolean(profile.skipSeriesSecondary),\n        allowedLanguages: normalizedAllowed,\n        minPages: profile.minPages ?? 0,\n        ignored: ignoredArray.slice().sort(),\n      };\n    };\n\n    const normalizedConfig = normalizeForComparison(resolvedConfig);\n    const normalizedServer = normalizeForComparison(serverProfile);\n\n    return compareObjectsCarr(normalizedServer, normalizedConfig).equal;\n  }\n\n  async calculateDiff(\n    profiles: InputConfigMetadataProfile[],\n    serverCache: ServerCache,\n  ): Promise<MetadataProfileDiff<MetadataProfileResource> | null> {\n    if (profiles == null) {\n      this.logger.debug(`Config 'metadata_profiles' not specified. Ignoring.`);\n      return null;\n    }\n\n    const serverData = await this.loadFromServer();\n\n    const missingOnServer: InputConfigMetadataProfile[] = [];\n    const changed: Array<{ config: InputConfigMetadataProfile; server: MetadataProfileResource }> = [];\n    const noChanges: MetadataProfileResource[] = [];\n\n    // Create maps for efficient lookup\n    const serverByName = new Map<string, MetadataProfileResource>();\n    serverData.forEach((profile) => {\n      if (profile.name) {\n        serverByName.set(profile.name, profile);\n      }\n    });\n\n    // Process each config profile\n    for (const configProfile of profiles) {\n      const serverProfile = serverByName.get(configProfile.name);\n\n      if (!serverProfile) {\n        // Profile doesn't exist on server\n        missingOnServer.push(configProfile);\n      } else {\n        // Profile exists, check if configuration matches\n        const resolvedConfig = await this.resolveConfig(configProfile, serverCache);\n        const isChanged = !this.isConfigEqual(resolvedConfig, serverProfile);\n        if (isChanged) {\n          changed.push({ config: configProfile, server: serverProfile });\n        } else {\n          noChanges.push(serverProfile);\n        }\n        // Remove from serverByName so we know it was managed\n        serverByName.delete(configProfile.name);\n      }\n    }\n\n    this.logger.debug({ missingOnServer, changed, noChanges }, \"Metadata profile comparison\");\n\n    if (missingOnServer.length === 0 && changed.length === 0) {\n      this.logger.debug(`Metadata profiles are in sync`);\n      return null;\n    }\n\n    this.logger.info(`Found ${missingOnServer.length + changed.length} differences for metadata profiles.`);\n\n    return {\n      missingOnServer,\n      changed,\n      noChanges,\n    };\n  }\n}\n"
  },
  {
    "path": "src/metadataProfiles/metadataProfileSyncer.ts",
    "content": "import { ServerCache } from \"../cache\";\nimport { logger } from \"../logger\";\nimport { ArrType } from \"../types/common.types\";\nimport { MergedConfigInstance } from \"../types/config.types\";\nimport { MetadataProfileSyncResult } from \"./metadataProfile.types\";\nimport { BaseMetadataProfileSync } from \"./metadataProfileBase\";\nimport { LidarrMetadataProfileSync } from \"./metadataProfileLidarr\";\nimport { ReadarrMetadataProfileSync } from \"./metadataProfileReadarr\";\n\nfunction createMetadataProfileSync(arrType: ArrType): BaseMetadataProfileSync | null {\n  switch (arrType) {\n    case \"LIDARR\":\n      return new LidarrMetadataProfileSync();\n    case \"READARR\":\n      return new ReadarrMetadataProfileSync();\n    default:\n      logger.debug(`Metadata profile synchronization is not supported for Arr type: ${arrType}`);\n      return null;\n  }\n}\n\n/**\n * Sync metadata profiles - handles add/update and deletion in one unified call\n * Takes the full config object to handle all scenarios\n */\nexport async function syncMetadataProfiles(arrType: ArrType, config: MergedConfigInstance, serverCache: ServerCache): Promise<void> {\n  const sync = createMetadataProfileSync(arrType);\n\n  if (sync == null) {\n    return;\n  }\n\n  await sync.syncMetadataProfiles(config, serverCache);\n}\n"
  },
  {
    "path": "src/quality-definitions.test.ts",
    "content": "import { describe, expect, test } from \"vitest\";\nimport { MergedQualityDefinitionResource } from \"./types/merged.types\";\nimport { calculateQualityDefinitionDiff, interpolateSize } from \"./quality-definitions\";\nimport { TrashQualityDefinition } from \"./types/trashguide.types\";\n\ndescribe(\"QualityDefinitions\", async () => {\n  const server: MergedQualityDefinitionResource[] = [\n    {\n      quality: {\n        id: 0,\n        name: \"Unknown\",\n        source: \"unknown\",\n        resolution: 0,\n      },\n      title: \"Unknown\",\n      weight: 1,\n      minSize: 1,\n      maxSize: 199.9,\n      preferredSize: 194.9,\n      id: 1,\n    },\n    {\n      quality: {\n        id: 1,\n        name: \"SDTV\",\n        source: \"television\",\n        resolution: 480,\n      },\n      title: \"SDTV\",\n      weight: 2,\n      minSize: 2,\n      maxSize: 100,\n      preferredSize: 95,\n      id: 2,\n    },\n  ];\n\n  const client = {\n    trash_id: \"aed34b9f60ee115dfa7918b742336277\",\n    type: \"movie\",\n    qualities: [\n      {\n        quality: \"SDTV\",\n        min: 2,\n        preferred: 95,\n        max: 100,\n      },\n    ],\n  };\n\n  test(\"calculateQualityDefinitionDiff - expect restData to always contain all server QDs\", async ({}) => {\n    const result = calculateQualityDefinitionDiff(server, client.qualities);\n\n    expect(result.restData.length).toBe(2);\n  });\n\n  test(\"calculateQualityDefinitionDiff - no diff\", async ({}) => {\n    const result = calculateQualityDefinitionDiff(server, client.qualities);\n\n    expect(result.changeMap.size).toBe(0);\n  });\n\n  test(\"calculateQualityDefinitionDiff - diff min size\", async ({}) => {\n    const clone: TrashQualityDefinition = JSON.parse(JSON.stringify(client));\n    clone.qualities[0]!.min = 3;\n\n    const result = calculateQualityDefinitionDiff(server, clone.qualities);\n\n    expect(result.changeMap.size).toBe(1);\n  });\n\n  test(\"calculateQualityDefinitionDiff - diff max size\", async ({}) => {\n    const clone: TrashQualityDefinition = JSON.parse(JSON.stringify(client));\n    clone.qualities[0]!.max = 3;\n\n    const result = calculateQualityDefinitionDiff(server, clone.qualities);\n\n    expect(result.changeMap.size).toBe(1);\n  });\n\n  test(\"calculateQualityDefinitionDiff - diff preferred size\", async ({}) => {\n    const clone: TrashQualityDefinition = JSON.parse(JSON.stringify(client));\n    clone.qualities[0]!.preferred = 3;\n\n    const result = calculateQualityDefinitionDiff(server, clone.qualities);\n\n    expect(result.changeMap.size).toBe(1);\n  });\n\n  test(\"calculateQualityDefinitionDiff - ignore not available qualities on server\", async ({}) => {\n    const clone: TrashQualityDefinition = JSON.parse(JSON.stringify(client));\n    clone.qualities[0]!.quality = \"New\";\n\n    const result = calculateQualityDefinitionDiff(server, clone.qualities);\n\n    expect(result.changeMap.size).toBe(0);\n    expect(result.restData.length).toBe(2);\n  });\n\n  test(\"interpolateSize - expected values\", async ({}) => {\n    expect(interpolateSize(0, 100, 50, 0.5)).toBe(50);\n    expect(interpolateSize(0, 100, 50, 0.0)).toBe(0);\n    expect(interpolateSize(0, 100, 50, 1.0)).toBe(100);\n    expect(interpolateSize(0, 100, 50, 0.25)).toBe(25);\n\n    expect(interpolateSize(2, 100, 95, 0.5)).toBe(95);\n    expect(interpolateSize(2, 100, 95, 0.0)).toBe(2);\n    expect(interpolateSize(2, 100, 95, 1.0)).toBe(100);\n  });\n\n  test(\"interpolateSize - should fail\", async ({}) => {\n    expect(() => interpolateSize(0, 100, 50, -0.5)).toThrowError();\n    expect(() => interpolateSize(0, 100, 50, 1.1)).toThrowError();\n  });\n});\n"
  },
  {
    "path": "src/quality-definitions.ts",
    "content": "import path from \"node:path\";\nimport { MergedQualityDefinitionResource } from \"./types/merged.types\";\nimport { getUnifiedClient } from \"./clients/unified-client\";\nimport { getEnvs } from \"./env\";\nimport { logger } from \"./logger\";\nimport { TrashQualityDefinitionQuality } from \"./types/trashguide.types\";\nimport { cloneWithJSON, loadJsonFile, roundToDecimal } from \"./util\";\n\nexport const loadQualityDefinitionFromServer = async (): Promise<MergedQualityDefinitionResource[]> => {\n  if (getEnvs().LOAD_LOCAL_SAMPLES) {\n    return loadJsonFile(path.resolve(__dirname, \"../tests/samples/qualityDefinition.json\"));\n  }\n  return await getUnifiedClient().getQualityDefinitions();\n};\n\nexport const calculateQualityDefinitionDiff = (\n  serverQDs: MergedQualityDefinitionResource[],\n  // TODO: this does not has to include all QDs right?\n  qualityDefinitions: TrashQualityDefinitionQuality[],\n  // TODO add config defined qualities\n) => {\n  const serverMap = serverQDs.reduce((p, c) => {\n    p.set(c.quality!.name!, c);\n    return p;\n  }, new Map<string, MergedQualityDefinitionResource>());\n\n  const changeMap = new Map<string, string[]>();\n  const restData: MergedQualityDefinitionResource[] = [];\n\n  const missingServerQualities = new Map(serverMap);\n\n  const mergedQualities = Object.values(\n    qualityDefinitions.toReversed().reduce<{ [k: string]: TrashQualityDefinitionQuality }>((p, c) => {\n      if (p[c.quality] != null) {\n        logger.debug(`QualityDefinition: Found duplicate for '${c.quality}'.`);\n      } else {\n        p[c.quality] = c;\n        missingServerQualities.delete(c.quality);\n      }\n\n      return p;\n    }, {}),\n  );\n\n  for (const quality of mergedQualities) {\n    const clonedQuality = cloneWithJSON(quality);\n    const serverQuality = serverMap.get(clonedQuality.quality);\n\n    if (serverQuality) {\n      const newData = cloneWithJSON(serverQuality);\n\n      const changes: string[] = [];\n\n      if (clonedQuality.min != null && serverQuality.minSize !== clonedQuality.min) {\n        changes.push(`MinSize diff: Server ${serverQuality.minSize} - Config ${clonedQuality.min}`);\n        newData.minSize = clonedQuality.min;\n      }\n      if (clonedQuality.max != null && serverQuality.maxSize !== clonedQuality.max) {\n        changes.push(`MaxSize diff: Server ${serverQuality.maxSize} - Config ${clonedQuality.max}`);\n        newData.maxSize = clonedQuality.max;\n      }\n\n      if (clonedQuality.preferred && serverQuality.preferredSize !== clonedQuality.preferred) {\n        changes.push(`PreferredSize diff: Server ${serverQuality.preferredSize} - Config ${clonedQuality.preferred}`);\n        newData.preferredSize = clonedQuality.preferred;\n      }\n\n      if (clonedQuality.title && serverQuality.title !== clonedQuality.title) {\n        changes.push(`Title diff: Server '${serverQuality.title}' - Config '${clonedQuality.title}'`);\n        newData.title = clonedQuality.title;\n      }\n\n      if (changes.length > 0) {\n        changeMap.set(serverQuality.quality!.name!, changes);\n        restData.push(newData);\n      } else {\n        restData.push(serverQuality);\n      }\n    } else {\n      logger.warn(`QualityDefinition: Found definition which is not available in server '${clonedQuality.quality}'. Ignoring.`);\n    }\n  }\n\n  if (missingServerQualities.size > 0) {\n    logger.debug(\n      `QualityDefinition: Found missing qualities will reuse server data: '${Array.from(missingServerQualities.values().map((e) => e.quality?.name || e.title))}'`,\n    );\n    restData.push(...missingServerQualities.values());\n  }\n\n  if (changeMap.size > 0) {\n    logger.debug(Object.fromEntries(changeMap.entries()), `QualityDefinition diffs:`);\n  }\n\n  return { changeMap, restData };\n};\n\nexport function interpolateSize(min: number, max: number, pref: number, ratio: number): number {\n  if (ratio < 0 || ratio > 1) {\n    throw new Error(`Unexpected ratio range. Should be between 0 <= ratio <= 1`);\n  }\n  if (ratio <= 0.5) {\n    // Interpolate between min and pref\n    return roundToDecimal(min + (pref - min) * (ratio / 0.5), 1);\n  } else {\n    // Interpolate between pref and max\n    return roundToDecimal(pref + (max - pref) * ((ratio - 0.5) / 0.5), 1);\n  }\n}\n"
  },
  {
    "path": "src/quality-profiles.test.ts",
    "content": "import path from \"path\";\nimport { beforeEach, afterEach, describe, expect, test, vi } from \"vitest\";\nimport * as uclient from \"./clients/unified-client\";\nimport * as log from \"./logger\";\nimport {\n  MergedCustomFormatResource,\n  MergedQualityDefinitionResource,\n  MergedQualityProfileQualityItemResource,\n  MergedQualityProfileResource,\n} from \"./types/merged.types\";\nimport { ServerCache } from \"./cache\";\nimport {\n  calculateQualityProfilesDiff,\n  checkForConflictingCFs,\n  deleteAllQualityProfiles,\n  deleteQualityProfile,\n  getUnmanagedQualityProfiles,\n  isOrderOfConfigQualitiesEqual,\n  isOrderOfQualitiesEqual,\n  loadQualityProfilesFromServer,\n  mapQualities,\n  mapQualityProfiles,\n} from \"./quality-profiles\";\nimport { CFProcessing } from \"./types/common.types\";\nimport { ConfigQualityProfile, ConfigQualityProfileItem, MergedConfigInstance } from \"./types/config.types\";\nimport { cloneWithJSON, loadJsonFile } from \"./util\";\n\ndescribe(\"QualityProfiles\", async () => {\n  const sampleQualityProfile = loadJsonFile<MergedQualityProfileResource>(\n    path.resolve(__dirname, `../tests/samples/single_quality_profile.json`),\n  );\n\n  const sampleQualityDefinitions = loadJsonFile<MergedQualityDefinitionResource[]>(\n    path.resolve(__dirname, `../tests/samples/qualityDefinition.json`),\n  );\n\n  const sampleCustomFormat = loadJsonFile<MergedCustomFormatResource>(\n    path.resolve(__dirname, `../tests/samples/single_custom_format.json`),\n  );\n\n  test(\"isOrderOfConfigQualitiesEqual - should match\", async ({}) => {\n    const fromConfig: ConfigQualityProfileItem[] = [\n      { name: \"WEB 1080p\", qualities: [\"WEBDL-1080p\", \"WEBRip-1080p\"] },\n      { name: \"HDTV-1080p\" },\n      { name: \"Bluray-1080p\" },\n      { name: \"Remux-1080p\" },\n      { name: \"WEB 720p\", qualities: [\"WEBDL-720p\", \"WEBRip-720p\"] },\n      { name: \"HDTV-720p\" },\n    ];\n    const fromServer: ConfigQualityProfileItem[] = [\n      { name: \"WEB 1080p\", qualities: [\"WEBDL-1080p\", \"WEBRip-1080p\"] },\n      { name: \"HDTV-1080p\" },\n      { name: \"Bluray-1080p\" },\n      { name: \"Remux-1080p\" },\n      { name: \"WEB 720p\", qualities: [\"WEBDL-720p\", \"WEBRip-720p\"] },\n      { name: \"HDTV-720p\" },\n    ];\n    const result = isOrderOfConfigQualitiesEqual(fromConfig, fromServer);\n\n    expect(result).toBe(true);\n  });\n\n  test(\"isOrderOfConfigQualitiesEqual - different order\", async ({}) => {\n    const fromConfig: ConfigQualityProfileItem[] = [\n      { name: \"WEB 1080p\", qualities: [\"WEBDL-1080p\", \"WEBRip-1080p\"] },\n      { name: \"HDTV-1080p\" },\n      { name: \"Bluray-1080p\" },\n      { name: \"Remux-1080p\" },\n      { name: \"WEB 720p\", qualities: [\"WEBDL-720p\", \"WEBRip-720p\"] },\n      { name: \"HDTV-720p\" },\n    ];\n    const fromServer: ConfigQualityProfileItem[] = [\n      { name: \"Bluray-1080p\", qualities: [] },\n      { name: \"HDTV-720p\", qualities: [] },\n      { name: \"WEB 720p\", qualities: [\"WEBDL-720p\", \"WEBRip-720p\"] },\n      { name: \"HDTV-1080p\", qualities: [] },\n      { name: \"WEB 1080p\", qualities: [\"WEBDL-1080p\", \"WEBRip-1080p\"] },\n      { name: \"Remux-1080p\", qualities: [] },\n    ];\n    const result = isOrderOfConfigQualitiesEqual(fromConfig, fromServer);\n\n    expect(result).toBe(false);\n  });\n\n  test(\"mapQualities - enabled correctly mapped (default sorting)\", async ({}) => {\n    const fromConfig: ConfigQualityProfileItem[] = [\n      { name: \"WEB 1080p\", qualities: [\"WEBDL-1080p\", \"WEBRip-1080p\"] },\n      { name: \"HDTV-1080p\" },\n    ];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 1, title: \"HDTV-1080p\", weight: 2, quality: { id: 1, name: \"HDTV-1080p\" } },\n      { id: 2, title: \"WEBDL-1080p\", weight: 2, quality: { id: 2, name: \"WEBDL-1080p\" } },\n      { id: 3, title: \"WEBRip-1080p\", weight: 2, quality: { id: 3, name: \"WEBRip-1080p\" } },\n      { id: 4, title: \"Unknown\", weight: 2, quality: { id: 4, name: \"Unknown\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"hi\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      upgrade: { allowed: true, until_quality: \"yes\", until_score: 5 },\n      score_set: \"default\",\n    };\n\n    const result = mapQualities(resources, profile);\n\n    expect(result).toHaveLength(3);\n    // ordering matters\n\n    expect(result[0]!.quality?.name).toBe(\"Unknown\");\n    expect(result[0]!.allowed).toBe(false);\n    expect(result[1]!.quality?.name).toBe(\"HDTV-1080p\");\n    expect(result[1]!.allowed).toBe(true);\n    expect(result[2]!.name).toBe(\"WEB 1080p\");\n    expect(result[2]!.allowed).toBe(true);\n  });\n\n  test(\"mapQualities - enabled mapped to false\", async ({}) => {\n    const fromConfig: ConfigQualityProfileItem[] = [\n      { name: \"WEB 1080p\", qualities: [\"WEBDL-1080p\", \"WEBRip-1080p\"] },\n      { name: \"HDTV-1080p\", enabled: false },\n    ];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 1, title: \"HDTV-1080p\", weight: 2, quality: { id: 1, name: \"HDTV-1080p\" } },\n      { id: 2, title: \"WEBDL-1080p\", weight: 2, quality: { id: 2, name: \"WEBDL-1080p\" } },\n      { id: 3, title: \"WEBRip-1080p\", weight: 2, quality: { id: 3, name: \"WEBRip-1080p\" } },\n      { id: 4, title: \"Unknown\", weight: 2, quality: { id: 4, name: \"Unknown\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"hi\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      upgrade: { allowed: true, until_quality: \"yes\", until_score: 5 },\n      score_set: \"default\",\n    };\n\n    const result = mapQualities(resources, profile);\n\n    expect(result).toHaveLength(3);\n\n    expect(result[0]!.quality?.name).toBe(\"Unknown\");\n    expect(result[0]!.allowed).toBe(false);\n    expect(result[1]!.quality?.name).toBe(\"HDTV-1080p\");\n    expect(result[1]!.allowed).toBe(false);\n    expect(result[2]!.name).toBe(\"WEB 1080p\");\n    expect(result[2]!.allowed).toBe(true);\n  });\n\n  test(\"mapQualities - ordering without nested qualities\", async ({}) => {\n    const fromConfig: ConfigQualityProfileItem[] = [\n      { name: \"WEB 1080p\", qualities: [\"WEBDL-1080p\", \"WEBRip-1080p\"] },\n      { name: \"HDTV-1080p\" },\n    ];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 1, title: \"HDTV-1080p\", weight: 2, quality: { id: 1, name: \"HDTV-1080p\" } },\n      { id: 2, title: \"WEBDL-1080p\", weight: 2, quality: { id: 2, name: \"WEBDL-1080p\" } },\n      { id: 3, title: \"WEBRip-1080p\", weight: 2, quality: { id: 3, name: \"WEBRip-1080p\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"Test Profile\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 5 },\n      score_set: \"default\",\n    };\n\n    const result = mapQualities(resources, profile);\n\n    expect(result).toHaveLength(2);\n    // ordering matters. Needs to be reversed for the API\n    expect(result[0]!.quality?.name).toBe(\"HDTV-1080p\");\n    expect(result[1]!.name).toBe(\"WEB 1080p\");\n  });\n\n  test(\"mapQualities - ordering with nested qualities\", async ({}) => {\n    const fromConfig: ConfigQualityProfileItem[] = [{ name: \"HD Group\", qualities: [\"HDTV-1080p\", \"WEBDL-1080p\"] }];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 1, title: \"HDTV-1080p\", weight: 2, quality: { id: 1, name: \"HDTV-1080p\" } },\n      { id: 2, title: \"WEBDL-1080p\", weight: 2, quality: { id: 2, name: \"WEBDL-1080p\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"Test Profile\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 5 },\n      score_set: \"default\",\n    };\n\n    const result = mapQualities(resources, profile);\n\n    expect(result).toHaveLength(1);\n    expect(result[0]!.name).toBe(\"HD Group\");\n    expect(result[0]!.items).toHaveLength(2);\n    expect(result[0]!.items![0]!.quality?.name).toBe(\"WEBDL-1080p\");\n    expect(result[0]!.items![1]!.quality?.name).toBe(\"HDTV-1080p\");\n  });\n\n  test(\"mapQualities - ordering with both nested and non-nested qualities\", async ({}) => {\n    const fromConfig: ConfigQualityProfileItem[] = [\n      { name: \"HD Group\", qualities: [\"HDTV-1080p\", \"WEBDL-1080p\"] },\n      { name: \"WEB 720p\", qualities: [\"WEBDL-720p\", \"WEBRip-720p\"] },\n    ];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 1, title: \"HDTV-1080p\", weight: 2, quality: { id: 1, name: \"HDTV-1080p\" } },\n      { id: 2, title: \"WEBDL-1080p\", weight: 2, quality: { id: 2, name: \"WEBDL-1080p\" } },\n      { id: 3, title: \"WEBDL-720p\", weight: 2, quality: { id: 3, name: \"WEBDL-720p\" } },\n      { id: 4, title: \"WEBRip-720p\", weight: 2, quality: { id: 4, name: \"WEBRip-720p\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"Test Profile\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 5 },\n      score_set: \"default\",\n    };\n\n    const result = mapQualities(resources, profile);\n\n    expect(result).toHaveLength(2);\n    expect(result[0]!.name).toBe(\"WEB 720p\");\n    expect(result[0]!.items).toHaveLength(2);\n    expect(result[0]!.items![0]!.quality?.name).toBe(\"WEBRip-720p\");\n    expect(result[0]!.items![1]!.quality?.name).toBe(\"WEBDL-720p\");\n\n    expect(result[1]!.name).toBe(\"HD Group\");\n    expect(result[1]!.items).toHaveLength(2);\n    expect(result[1]!.items![0]!.quality?.name).toBe(\"WEBDL-1080p\");\n    expect(result[1]!.items![1]!.quality?.name).toBe(\"HDTV-1080p\");\n  });\n\n  test(\"mapQualities - missing qualities added\", async ({}) => {\n    const fromConfig: ConfigQualityProfileItem[] = [\n      { name: \"WEB 1080p\", qualities: [\"WEBDL-1080p\", \"WEBRip-1080p\"] },\n      { name: \"HDTV-1080p\" },\n    ];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 1, title: \"HDTV-1080p\", weight: 2, quality: { id: 1, name: \"HDTV-1080p\" } },\n      { id: 2, title: \"WEBDL-1080p\", weight: 2, quality: { id: 2, name: \"WEBDL-1080p\" } },\n      { id: 3, title: \"WEBRip-1080p\", weight: 2, quality: { id: 3, name: \"WEBRip-1080p\" } },\n      { id: 4, title: \"Unknown\", weight: 2, quality: { id: 4, name: \"Unknown\" } },\n      { id: 5, title: \"Test\", weight: 2, quality: { id: 5, name: \"Test\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"hi\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      upgrade: { allowed: true, until_quality: \"yes\", until_score: 5 },\n      score_set: \"default\",\n    };\n\n    const result = mapQualities(resources, profile);\n\n    expect(result).toHaveLength(4);\n    // ordering matters\n    expect(result[0]!.quality?.name).toBe(\"Unknown\");\n    expect(result[0]!.allowed).toBe(false);\n    expect(result[1]!.quality?.name).toBe(\"Test\");\n    expect(result[1]!.allowed).toBe(false);\n    expect(result[2]!.quality?.name).toBe(\"HDTV-1080p\");\n    expect(result[2]!.allowed).toBe(true);\n    expect(result[3]!.name).toBe(\"WEB 1080p\");\n    expect(result[3]!.allowed).toBe(true);\n  });\n\n  test(\"calculateQualityProfilesDiff - should diff if minUpgradeFormatScore / minFormatScore is different\", async ({}) => {\n    const cfMap: CFProcessing = { carrIdMapping: new Map(), cfNameToCarrConfig: new Map() };\n\n    const fromConfig: ConfigQualityProfileItem[] = [{ name: \"HDTV-1080p\", enabled: false }];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 1, title: \"HDTV-1080p\", weight: 2, quality: { id: 1, name: \"HDTV-1080p\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"hi\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n      score_set: \"default\",\n    };\n\n    const config: MergedConfigInstance = {\n      custom_formats: [],\n      quality_profiles: [profile],\n      customFormatDefinitions: [],\n      media_management: {},\n      media_naming: {},\n    };\n\n    const serverProfile = cloneWithJSON(sampleQualityProfile);\n    serverProfile.name = \"hi\";\n    serverProfile.formatItems = [];\n    serverProfile.minUpgradeFormatScore = 3;\n    serverProfile.minFormatScore = 3;\n    serverProfile.cutoff = 1;\n    serverProfile.items = [{ allowed: false, items: [], quality: { id: 1, name: \"HDTV-1080p\" } }];\n\n    const serverQP: MergedQualityProfileResource[] = [serverProfile];\n    const serverQD: MergedQualityDefinitionResource[] = resources;\n    const serverCF: MergedCustomFormatResource[] = [cloneWithJSON(sampleCustomFormat)];\n\n    const serverCache = new ServerCache(serverQD, serverQP, serverCF, []);\n\n    let diff = await calculateQualityProfilesDiff(\"RADARR\", cfMap, config, serverCache);\n    expect(diff.changedQPs.length).toBe(1);\n    expect(diff.create.length).toBe(0);\n    expect(diff.noChanges.length).toBe(0);\n\n    serverProfile.minFormatScore = 0;\n    diff = await calculateQualityProfilesDiff(\"RADARR\", cfMap, config, serverCache);\n    expect(diff.changedQPs.length).toBe(1);\n    expect(diff.create.length).toBe(0);\n    expect(diff.noChanges.length).toBe(0);\n\n    serverProfile.minUpgradeFormatScore = 0;\n    diff = await calculateQualityProfilesDiff(\"RADARR\", cfMap, config, serverCache);\n    expect(diff.changedQPs.length).toBe(1);\n    expect(diff.create.length).toBe(0);\n    expect(diff.noChanges.length).toBe(0);\n\n    profile.min_format_score = 0;\n    serverProfile.minFormatScore = 1;\n    diff = await calculateQualityProfilesDiff(\"RADARR\", cfMap, config, serverCache);\n    expect(diff.changedQPs.length).toBe(1);\n    expect(diff.create.length).toBe(0);\n    expect(diff.noChanges.length).toBe(0);\n  });\n\n  test(\"calculateQualityProfilesDiff - should not diff if minFormatScore is equal\", async ({}) => {\n    const cfMap: CFProcessing = { carrIdMapping: new Map(), cfNameToCarrConfig: new Map() };\n\n    const fromConfig: ConfigQualityProfileItem[] = [{ name: \"HDTV-1080p\", enabled: false }];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 1, title: \"HDTV-1080p\", weight: 2, quality: { id: 1, name: \"HDTV-1080p\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"hi\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n      score_set: \"default\",\n    };\n\n    const config: MergedConfigInstance = {\n      custom_formats: [],\n      quality_profiles: [profile],\n      customFormatDefinitions: [],\n      media_management: {},\n      media_naming: {},\n    };\n\n    const serverProfile = cloneWithJSON(sampleQualityProfile);\n    serverProfile.name = \"hi\";\n    serverProfile.formatItems = [];\n    serverProfile.minUpgradeFormatScore = 3;\n    serverProfile.minFormatScore = 2;\n    serverProfile.cutoff = 1;\n    serverProfile.items = [{ allowed: false, items: [], quality: { id: 1, name: \"HDTV-1080p\" } }];\n\n    const serverQP: MergedQualityProfileResource[] = [serverProfile];\n    const serverQD: MergedQualityDefinitionResource[] = resources;\n    const serverCF: MergedCustomFormatResource[] = [cloneWithJSON(sampleCustomFormat)];\n\n    const serverCache = new ServerCache(serverQD, serverQP, serverCF, []);\n\n    const diff = await calculateQualityProfilesDiff(\"RADARR\", cfMap, config, serverCache);\n    expect(diff.changedQPs.length).toBe(0);\n    expect(diff.create.length).toBe(0);\n    expect(diff.noChanges.length).toBe(1);\n  });\n\n  test(\"calculateQualityProfilesDiff - should not diff if minUpgradeFormatScore is not configured\", async ({}) => {\n    // TODO\n  });\n\n  test(\"calculateQualityProfilesDiff - should diff for languageChange (radarr)\", async ({}) => {\n    const cfMap: CFProcessing = { carrIdMapping: new Map(), cfNameToCarrConfig: new Map() };\n\n    const fromConfig: ConfigQualityProfileItem[] = [{ name: \"HDTV-1080p\", enabled: false }];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 1, title: \"HDTV-1080p\", weight: 2, quality: { id: 1, name: \"HDTV-1080p\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"hi\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n      score_set: \"default\",\n      language: \"Any\",\n    };\n\n    const config: MergedConfigInstance = {\n      custom_formats: [],\n      quality_profiles: [profile],\n      customFormatDefinitions: [],\n      media_management: {},\n      media_naming: {},\n    };\n\n    const serverProfile = cloneWithJSON(sampleQualityProfile);\n    serverProfile.name = \"hi\";\n    serverProfile.formatItems = [];\n    serverProfile.minUpgradeFormatScore = 3;\n    serverProfile.minFormatScore = 2;\n    serverProfile.cutoff = 1;\n    serverProfile.items = [{ allowed: false, items: [], quality: { id: 1, name: \"HDTV-1080p\" } }];\n    serverProfile.language = { id: 1, name: \"English\" };\n\n    const serverQP: MergedQualityProfileResource[] = [serverProfile];\n    const serverQD: MergedQualityDefinitionResource[] = resources;\n    const serverCF: MergedCustomFormatResource[] = [cloneWithJSON(sampleCustomFormat)];\n\n    const serverCache = new ServerCache(serverQD, serverQP, serverCF, [{ id: 0, name: \"Any\" }]);\n\n    const diff = await calculateQualityProfilesDiff(\"RADARR\", cfMap, config, serverCache);\n    expect(diff.changedQPs.length).toBe(1);\n    expect(diff.create.length).toBe(0);\n    expect(diff.noChanges.length).toBe(0);\n  });\n\n  test(\"calculateQualityProfilesDiff - should not diff for language (radarr)\", async ({}) => {\n    const cfMap: CFProcessing = { carrIdMapping: new Map(), cfNameToCarrConfig: new Map() };\n\n    const fromConfig: ConfigQualityProfileItem[] = [{ name: \"HDTV-1080p\", enabled: false }];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 1, title: \"HDTV-1080p\", weight: 2, quality: { id: 1, name: \"HDTV-1080p\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"hi\",\n      min_format_score: 2,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n      score_set: \"default\",\n      language: \"Any\",\n    };\n\n    const config: MergedConfigInstance = {\n      custom_formats: [],\n      quality_profiles: [profile],\n      customFormatDefinitions: [],\n      media_management: {},\n      media_naming: {},\n    };\n\n    const serverProfile = cloneWithJSON(sampleQualityProfile);\n    serverProfile.name = \"hi\";\n    serverProfile.formatItems = [];\n    serverProfile.minUpgradeFormatScore = 3;\n    serverProfile.minFormatScore = 2;\n    serverProfile.cutoff = 1;\n    serverProfile.items = [{ allowed: false, items: [], quality: { id: 1, name: \"HDTV-1080p\" } }];\n    serverProfile.language = { id: 0, name: \"Any\" };\n\n    const serverQP: MergedQualityProfileResource[] = [serverProfile];\n    const serverQD: MergedQualityDefinitionResource[] = resources;\n    const serverCF: MergedCustomFormatResource[] = [cloneWithJSON(sampleCustomFormat)];\n\n    const serverCache = new ServerCache(serverQD, serverQP, serverCF, [{ id: 0, name: \"Any\" }]);\n\n    const diff = await calculateQualityProfilesDiff(\"RADARR\", cfMap, config, serverCache);\n    expect(diff.changedQPs.length).toBe(0);\n    expect(diff.create.length).toBe(0);\n    expect(diff.noChanges.length).toBe(1);\n  });\n\n  test(\"calculateQualityProfilesDiff - should create profile with upgrade.allowed: false using until_quality as cutoff\", async ({}) => {\n    const cfMap: CFProcessing = { carrIdMapping: new Map(), cfNameToCarrConfig: new Map() };\n\n    // Mirrors the real-world config: groups + upgrade.allowed: false + until_quality pointing to a group\n    const fromConfig: ConfigQualityProfileItem[] = [\n      { name: \"WEB 720p\", qualities: [\"WEBDL-720p\", \"WEBRip-720p\"] },\n      { name: \"Bluray-1080p\" },\n      { name: \"WEB 1080p\", qualities: [\"WEBDL-1080p\", \"WEBRip-1080p\"] },\n    ];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 1, title: \"WEBDL-720p\", weight: 2, quality: { id: 1, name: \"WEBDL-720p\" } },\n      { id: 2, title: \"WEBRip-720p\", weight: 2, quality: { id: 2, name: \"WEBRip-720p\" } },\n      { id: 3, title: \"Bluray-1080p\", weight: 2, quality: { id: 3, name: \"Bluray-1080p\" } },\n      { id: 4, title: \"WEBDL-1080p\", weight: 2, quality: { id: 4, name: \"WEBDL-1080p\" } },\n      { id: 5, title: \"WEBRip-1080p\", weight: 2, quality: { id: 5, name: \"WEBRip-1080p\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"Test - No Upgrade\",\n      min_format_score: 5,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      // until_quality is a group: cutoff should map to the group's generated ID\n      upgrade: { allowed: false, until_quality: \"WEB 720p\", until_score: 0, min_format_score: 1 },\n      score_set: \"default\",\n    };\n\n    const config: MergedConfigInstance = {\n      custom_formats: [],\n      quality_profiles: [profile],\n      customFormatDefinitions: [],\n      media_management: {},\n      media_naming: {},\n    };\n\n    const serverCache = new ServerCache(resources, [], [], []);\n\n    const diff = await calculateQualityProfilesDiff(\"RADARR\", cfMap, config, serverCache);\n    expect(diff.changedQPs.length).toBe(0);\n    expect(diff.create.length).toBe(1);\n    expect(diff.noChanges.length).toBe(0);\n\n    const createdProfile = diff.create[0];\n    expect(createdProfile).toBeDefined();\n    expect(createdProfile?.upgradeAllowed).toBe(false);\n    expect(createdProfile?.cutoffFormatScore).toBe(1);\n    // cutoff should point to the \"WEB 720p\" group (1000 + index 0 in allowedQualities)\n    // The group ID for WEB 720p (first in the config) should be 1000\n    expect(createdProfile?.cutoff).toBe(1000);\n  });\n\n  test(\"calculateQualityProfilesDiff - should create profile with upgrade: {allowed: false} and no until_quality\", async ({}) => {\n    const cfMap: CFProcessing = { carrIdMapping: new Map(), cfNameToCarrConfig: new Map() };\n\n    // Mirrors the exact failing config from the issue: upgrade block with only allowed: false\n    const fromConfig: ConfigQualityProfileItem[] = [\n      { name: \"Remux-2160p\" },\n      { name: \"WEB 2160p\", qualities: [\"WEBDL-2160p\", \"WEBRip-2160p\"] },\n      { name: \"Remux-1080p\" },\n      { name: \"WEB 1080p\", qualities: [\"WEBDL-1080p\", \"WEBRip-1080p\"] },\n    ];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 10, title: \"Remux-2160p\", weight: 2, quality: { id: 10, name: \"Remux-2160p\" } },\n      { id: 11, title: \"WEBDL-2160p\", weight: 2, quality: { id: 11, name: \"WEBDL-2160p\" } },\n      { id: 12, title: \"WEBRip-2160p\", weight: 2, quality: { id: 12, name: \"WEBRip-2160p\" } },\n      { id: 13, title: \"Remux-1080p\", weight: 2, quality: { id: 13, name: \"Remux-1080p\" } },\n      { id: 14, title: \"WEBDL-1080p\", weight: 2, quality: { id: 14, name: \"WEBDL-1080p\" } },\n      { id: 15, title: \"WEBRip-1080p\", weight: 2, quality: { id: 15, name: \"WEBRip-1080p\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"ExampleInConfigProfile\",\n      min_format_score: 0,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      // No until_quality / until_score - should fall back to highest-priority quality\n      upgrade: { allowed: false },\n      score_set: \"default\",\n    };\n\n    const config: MergedConfigInstance = {\n      custom_formats: [],\n      quality_profiles: [profile],\n      customFormatDefinitions: [],\n      media_management: {},\n      media_naming: {},\n    };\n\n    const serverCache = new ServerCache(resources, [], [], []);\n\n    const diff = await calculateQualityProfilesDiff(\"RADARR\", cfMap, config, serverCache);\n    expect(diff.create.length).toBe(1);\n\n    const createdProfile = diff.create[0];\n    expect(createdProfile?.upgradeAllowed).toBe(false);\n    expect(createdProfile?.cutoffFormatScore).toBe(1);\n    // cutoff must not be 0/undefined — should fall back to Remux-2160p (first quality = id 10)\n    expect(createdProfile?.cutoff).toBeDefined();\n    expect(createdProfile?.cutoff).not.toBe(0);\n    expect(createdProfile?.cutoff).toBe(10); // Remux-2160p = highest priority quality\n  });\n\n  test(\"calculateQualityProfilesDiff - should update profile when changing upgrade.allowed from true to false (single quality)\", async ({}) => {\n    const cfMap: CFProcessing = { carrIdMapping: new Map(), cfNameToCarrConfig: new Map() };\n\n    const fromConfig: ConfigQualityProfileItem[] = [{ name: \"HDTV-1080p\" }];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 10, title: \"HDTV-1080p\", weight: 2, quality: { id: 10, name: \"HDTV-1080p\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"Test Update - Disable Upgrade\",\n      min_format_score: 0,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      upgrade: { allowed: false, until_quality: \"HDTV-1080p\", until_score: 0, min_format_score: 1 },\n      score_set: \"default\",\n    };\n\n    const config: MergedConfigInstance = {\n      custom_formats: [],\n      quality_profiles: [profile],\n      customFormatDefinitions: [],\n      media_management: {},\n      media_naming: {},\n    };\n\n    const serverProfile = cloneWithJSON(sampleQualityProfile);\n    serverProfile.name = \"Test Update - Disable Upgrade\";\n    serverProfile.formatItems = [];\n    serverProfile.upgradeAllowed = true;\n    serverProfile.cutoff = 99; // Old cutoff value that won't match\n    serverProfile.cutoffFormatScore = 1000;\n    serverProfile.minUpgradeFormatScore = 1;\n    serverProfile.items = [{ allowed: true, items: [], quality: { id: 10, name: \"HDTV-1080p\" } }];\n\n    const serverCache = new ServerCache(resources, [serverProfile], [], []);\n\n    const diff = await calculateQualityProfilesDiff(\"RADARR\", cfMap, config, serverCache);\n    expect(diff.changedQPs.length).toBe(1);\n    expect(diff.create.length).toBe(0);\n    expect(diff.noChanges.length).toBe(0);\n\n    const updatedProfile = diff.changedQPs[0];\n    expect(updatedProfile).toBeDefined();\n    // cutoff must point to the until_quality (HDTV-1080p = id 10)\n    expect(updatedProfile?.cutoff).toBe(10);\n    expect(updatedProfile?.upgradeAllowed).toBe(false);\n    expect(updatedProfile?.cutoffFormatScore).toBe(1);\n  });\n\n  test(\"calculateQualityProfilesDiff - should update profile when changing upgrade.allowed from true to false (quality group)\", async ({}) => {\n    const cfMap: CFProcessing = { carrIdMapping: new Map(), cfNameToCarrConfig: new Map() };\n\n    // Mirrors the real-world Radarr ExampleInConfigProfile case: group qualities + upgrade disabled\n    const fromConfig: ConfigQualityProfileItem[] = [\n      { name: \"Remux-2160p\" },\n      { name: \"WEB 2160p\", qualities: [\"WEBDL-2160p\", \"WEBRip-2160p\"] },\n      { name: \"Remux-1080p\" },\n      { name: \"WEB 1080p\", qualities: [\"WEBDL-1080p\", \"WEBRip-1080p\"] },\n    ];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 10, title: \"Remux-2160p\", weight: 2, quality: { id: 10, name: \"Remux-2160p\" } },\n      { id: 11, title: \"WEBDL-2160p\", weight: 2, quality: { id: 11, name: \"WEBDL-2160p\" } },\n      { id: 12, title: \"WEBRip-2160p\", weight: 2, quality: { id: 12, name: \"WEBRip-2160p\" } },\n      { id: 13, title: \"Remux-1080p\", weight: 2, quality: { id: 13, name: \"Remux-1080p\" } },\n      { id: 14, title: \"WEBDL-1080p\", weight: 2, quality: { id: 14, name: \"WEBDL-1080p\" } },\n      { id: 15, title: \"WEBRip-1080p\", weight: 2, quality: { id: 15, name: \"WEBRip-1080p\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"Test Update - Disable Upgrade Group\",\n      min_format_score: 0,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      // until_quality points to the WEB 2160p group\n      upgrade: { allowed: false, until_quality: \"WEB 2160p\", until_score: 0, min_format_score: 1 },\n      score_set: \"default\",\n    };\n\n    const config: MergedConfigInstance = {\n      custom_formats: [],\n      quality_profiles: [profile],\n      customFormatDefinitions: [],\n      media_management: {},\n      media_naming: {},\n    };\n\n    const serverProfile = cloneWithJSON(sampleQualityProfile);\n    serverProfile.name = \"Test Update - Disable Upgrade Group\";\n    serverProfile.formatItems = [];\n    serverProfile.upgradeAllowed = true;\n    serverProfile.cutoff = 15; // Old cutoff pointing to WEBRip-1080p\n    serverProfile.cutoffFormatScore = 1000;\n    serverProfile.minUpgradeFormatScore = 1;\n    serverProfile.items = [\n      { allowed: true, items: [], quality: { id: 10, name: \"Remux-2160p\" } },\n      {\n        allowed: true,\n        id: 1001,\n        name: \"WEB 2160p\",\n        items: [\n          { allowed: true, items: [], quality: { id: 12, name: \"WEBRip-2160p\" } },\n          { allowed: true, items: [], quality: { id: 11, name: \"WEBDL-2160p\" } },\n        ],\n      },\n      { allowed: true, items: [], quality: { id: 13, name: \"Remux-1080p\" } },\n      {\n        allowed: true,\n        id: 1003,\n        name: \"WEB 1080p\",\n        items: [\n          { allowed: true, items: [], quality: { id: 15, name: \"WEBRip-1080p\" } },\n          { allowed: true, items: [], quality: { id: 14, name: \"WEBDL-1080p\" } },\n        ],\n      },\n    ];\n\n    const serverCache = new ServerCache(resources, [serverProfile], [], []);\n\n    const diff = await calculateQualityProfilesDiff(\"RADARR\", cfMap, config, serverCache);\n    expect(diff.changedQPs.length).toBe(1);\n    expect(diff.create.length).toBe(0);\n    expect(diff.noChanges.length).toBe(0);\n\n    const updatedProfile = diff.changedQPs[0];\n    expect(updatedProfile).toBeDefined();\n    // cutoff must point to the until_quality group \"WEB 2160p\" = id 1001\n    expect(updatedProfile?.cutoff).toBe(1001);\n    expect(updatedProfile?.upgradeAllowed).toBe(false);\n    expect(updatedProfile?.cutoffFormatScore).toBe(1);\n  });\n\n  test(\"calculateQualityProfilesDiff - should update profile when changing upgrade.allowed from true to false with no until_quality\", async ({}) => {\n    const cfMap: CFProcessing = { carrIdMapping: new Map(), cfNameToCarrConfig: new Map() };\n\n    const fromConfig: ConfigQualityProfileItem[] = [{ name: \"Remux-2160p\" }, { name: \"Remux-1080p\" }];\n\n    const resources: MergedQualityDefinitionResource[] = [\n      { id: 10, title: \"Remux-2160p\", weight: 2, quality: { id: 10, name: \"Remux-2160p\" } },\n      { id: 13, title: \"Remux-1080p\", weight: 2, quality: { id: 13, name: \"Remux-1080p\" } },\n    ];\n\n    const profile: ConfigQualityProfile = {\n      name: \"Test Update - Disable Upgrade No Until\",\n      min_format_score: 0,\n      qualities: fromConfig,\n      quality_sort: \"top\",\n      // No until_quality — should fall back to highest-priority allowed quality (Remux-2160p = id 10)\n      upgrade: { allowed: false },\n      score_set: \"default\",\n    };\n\n    const config: MergedConfigInstance = {\n      custom_formats: [],\n      quality_profiles: [profile],\n      customFormatDefinitions: [],\n      media_management: {},\n      media_naming: {},\n    };\n\n    const serverProfile = cloneWithJSON(sampleQualityProfile);\n    serverProfile.name = \"Test Update - Disable Upgrade No Until\";\n    serverProfile.formatItems = [];\n    serverProfile.upgradeAllowed = true;\n    serverProfile.cutoff = 13; // Old cutoff pointing to Remux-1080p\n    serverProfile.cutoffFormatScore = 500;\n    serverProfile.minUpgradeFormatScore = 1;\n    serverProfile.items = [\n      { allowed: true, items: [], quality: { id: 10, name: \"Remux-2160p\" } },\n      { allowed: true, items: [], quality: { id: 13, name: \"Remux-1080p\" } },\n    ];\n\n    const serverCache = new ServerCache(resources, [serverProfile], [], []);\n\n    const diff = await calculateQualityProfilesDiff(\"RADARR\", cfMap, config, serverCache);\n    expect(diff.changedQPs.length).toBe(1);\n    expect(diff.create.length).toBe(0);\n\n    const updatedProfile = diff.changedQPs[0];\n    expect(updatedProfile).toBeDefined();\n    // cutoff falls back to Remux-2160p (highest-priority allowed quality = id 10)\n    expect(updatedProfile?.cutoff).toBe(10);\n    expect(updatedProfile?.upgradeAllowed).toBe(false);\n    expect(updatedProfile?.cutoffFormatScore).toBe(1);\n  });\n\n  describe(\"delete Quality Profiles tests\", () => {\n    beforeEach(() => {\n      vi.restoreAllMocks();\n      vi.clearAllMocks();\n    });\n\n    afterEach(() => {\n      vi.restoreAllMocks();\n    });\n\n    test(\"deleteAllQualityProfiles() deletes every quality profile returned by server\", async () => {\n      // Arrange\n      const qp1 = cloneWithJSON(sampleQualityProfile);\n      qp1.id = 1001;\n      qp1.name = \"QP-1\";\n      const qp2 = cloneWithJSON(sampleQualityProfile);\n      qp2.id = 1002;\n      qp2.name = \"QP-2\";\n      const qp3 = cloneWithJSON(sampleQualityProfile);\n      qp3.id = 1003;\n      qp3.name = \"QP-3\";\n\n      const deleteFn = vi.fn().mockResolvedValue(undefined);\n      const getFn = vi.fn().mockResolvedValue([qp1, qp2, qp3]);\n\n      vi.spyOn(uclient, \"getUnifiedClient\").mockReturnValue({\n        getQualityProfiles: getFn,\n        deleteQualityProfile: deleteFn,\n      } as any);\n\n      const logSpy = vi.spyOn(log.logger, \"info\").mockImplementation(() => {});\n\n      // Act\n      await deleteAllQualityProfiles();\n\n      // Assert\n      expect(deleteFn).toHaveBeenCalledTimes(3);\n      expect(deleteFn).toHaveBeenNthCalledWith(1, \"1001\");\n      expect(deleteFn).toHaveBeenNthCalledWith(2, \"1002\");\n      expect(deleteFn).toHaveBeenNthCalledWith(3, \"1003\");\n\n      expect(logSpy).toHaveBeenCalledWith(\"Deleted QP: 'QP-1'\");\n      expect(logSpy).toHaveBeenCalledWith(\"Deleted QP: 'QP-2'\");\n      expect(logSpy).toHaveBeenCalledWith(\"Deleted QP: 'QP-3'\");\n    });\n\n    test(\"when no profiles then no deletions by deleteAllQualityProfiles\", async () => {\n      // Arrange\n      const deleteFn = vi.fn();\n      const getFn = vi.fn().mockResolvedValue([] as any[]);\n\n      vi.spyOn(uclient, \"getUnifiedClient\").mockReturnValue({\n        getQualityProfiles: getFn,\n        deleteQualityProfile: deleteFn,\n      } as any);\n\n      // Act\n      await deleteAllQualityProfiles();\n\n      // Assert\n      expect(getFn).toHaveBeenCalledTimes(1);\n      expect(deleteFn).not.toHaveBeenCalled();\n    });\n\n    test(\"deleteQualityProfile() deletes only the given quality profile id\", async () => {\n      // Arrange\n      const qp1 = cloneWithJSON(sampleQualityProfile);\n      qp1.id = 1001;\n      qp1.name = \"QP-1\";\n      const qp2 = cloneWithJSON(sampleQualityProfile);\n      qp2.id = 1002;\n      qp2.name = \"QP-2\";\n      const qp3 = cloneWithJSON(sampleQualityProfile);\n      qp3.id = 1003;\n      qp3.name = \"QP-3\";\n\n      const deleteFn = vi.fn().mockResolvedValue(undefined);\n\n      vi.spyOn(uclient, \"getUnifiedClient\").mockReturnValue({\n        deleteQualityProfile: deleteFn,\n      } as any);\n\n      const logSpy = vi.spyOn(log.logger, \"info\").mockImplementation(() => {});\n\n      // Act\n      await deleteQualityProfile(qp1);\n\n      // Assert\n      expect(deleteFn).toHaveBeenCalledTimes(1);\n      expect(deleteFn).toHaveBeenNthCalledWith(1, \"1001\");\n      expect(deleteFn).not.toHaveBeenCalledWith(\"1002\");\n      expect(deleteFn).not.toHaveBeenCalledWith(\"1003\");\n\n      expect(logSpy).toHaveBeenCalledWith(\"Deleted QP: 'QP-1'\");\n      expect(logSpy).not.toHaveBeenCalledWith(\"Deleted QP: 'QP-2'\");\n      expect(logSpy).not.toHaveBeenCalledWith(\"Deleted QP: 'QP-3'\");\n    });\n  });\n\n  describe(\"isOrderOfQualitiesEqual\", async () => {\n    test(\"should diff for grouped incorrect order\", async ({}) => {\n      const arr1: MergedQualityProfileQualityItemResource[] = [\n        {\n          allowed: true,\n          id: 1000,\n          name: \"Merged QPs\",\n          items: [\n            {\n              quality: {\n                id: 14,\n                name: \"WEBRip-720p\",\n                resolution: 720,\n                source: \"webrip\",\n              },\n              allowed: true,\n              items: [],\n            },\n            {\n              quality: {\n                id: 5,\n                name: \"WEBDL-720p\",\n                resolution: 720,\n                source: \"webdl\",\n              },\n              allowed: true,\n              items: [],\n            },\n            {\n              quality: {\n                id: 6,\n                name: \"Bluray-720p\",\n                resolution: 720,\n                source: \"bluray\",\n              },\n              allowed: true,\n              items: [],\n            },\n            {\n              quality: {\n                id: 3,\n                name: \"WEBDL-1080p\",\n                resolution: 1080,\n                source: \"webdl\",\n              },\n              allowed: true,\n              items: [],\n            },\n            {\n              quality: {\n                id: 15,\n                name: \"WEBRip-1080p\",\n                resolution: 1080,\n                source: \"webrip\",\n              },\n              allowed: true,\n              items: [],\n            },\n            {\n              quality: {\n                id: 7,\n                name: \"Bluray-1080p\",\n                resolution: 1080,\n                source: \"bluray\",\n              },\n              allowed: true,\n              items: [],\n            },\n          ],\n        },\n      ];\n\n      const arr2: MergedQualityProfileQualityItemResource[] = [\n        {\n          name: \"Merged QPs\",\n          items: [\n            {\n              quality: {\n                id: 7,\n                name: \"Bluray-1080p\",\n                source: \"bluray\",\n                resolution: 1080,\n              },\n              items: [],\n              allowed: true,\n            },\n            {\n              quality: {\n                id: 15,\n                name: \"WEBRip-1080p\",\n                source: \"webrip\",\n                resolution: 1080,\n              },\n              items: [],\n              allowed: true,\n            },\n            {\n              quality: {\n                id: 3,\n                name: \"WEBDL-1080p\",\n                source: \"webdl\",\n                resolution: 1080,\n              },\n              items: [],\n              allowed: true,\n            },\n            {\n              quality: {\n                id: 6,\n                name: \"Bluray-720p\",\n                source: \"bluray\",\n                resolution: 720,\n              },\n              items: [],\n              allowed: true,\n            },\n            {\n              quality: {\n                id: 5,\n                name: \"WEBDL-720p\",\n                source: \"webdl\",\n                resolution: 720,\n              },\n              items: [],\n              allowed: true,\n            },\n            {\n              quality: {\n                id: 14,\n                name: \"WEBRip-720p\",\n                source: \"webrip\",\n                resolution: 720,\n              },\n              items: [],\n              allowed: true,\n            },\n          ],\n          allowed: true,\n          id: 1000,\n        },\n      ];\n\n      expect(isOrderOfQualitiesEqual(arr1, arr2)).toBe(false);\n    });\n\n    test(\"should diff for incorrect quality order\", async ({}) => {\n      const arr1: MergedQualityProfileQualityItemResource[] = [\n        {\n          allowed: true,\n          quality: {\n            id: 14,\n            name: \"WEBRip-720p\",\n            resolution: 720,\n            source: \"webrip\",\n          },\n        },\n        {\n          allowed: true,\n          quality: {\n            id: 15,\n            name: \"WEBRip-1080p\",\n            resolution: 1080,\n            source: \"webrip\",\n          },\n        },\n      ];\n\n      const arr2: MergedQualityProfileQualityItemResource[] = [\n        {\n          allowed: true,\n          quality: {\n            id: 15,\n            name: \"WEBRip-1080p\",\n            resolution: 1080,\n            source: \"webrip\",\n          },\n        },\n        {\n          allowed: true,\n          quality: {\n            id: 14,\n            name: \"WEBRip-720p\",\n            resolution: 720,\n            source: \"webrip\",\n          },\n        },\n      ];\n\n      expect(isOrderOfQualitiesEqual(arr1, arr2)).toBe(false);\n    });\n\n    test(\"should be equal 1\", async ({}) => {\n      const arr1: MergedQualityProfileQualityItemResource[] = [\n        {\n          allowed: true,\n          quality: {\n            id: 14,\n            name: \"WEBRip-720p\",\n            resolution: 720,\n            source: \"webrip\",\n          },\n        },\n      ];\n\n      const arr2: MergedQualityProfileQualityItemResource[] = [\n        {\n          allowed: true,\n          quality: {\n            id: 14,\n            name: \"WEBRip-720p\",\n            resolution: 720,\n            source: \"webrip\",\n          },\n        },\n      ];\n\n      expect(isOrderOfQualitiesEqual(arr1, arr2)).toBe(true);\n    });\n\n    test(\"should be equal 2\", async ({}) => {\n      const arr1: MergedQualityProfileQualityItemResource[] = [\n        {\n          allowed: true,\n          id: 1000,\n          name: \"Merged QPs\",\n          items: [\n            {\n              quality: {\n                id: 14,\n                name: \"WEBRip-720p\",\n                resolution: 720,\n                source: \"webrip\",\n              },\n              allowed: true,\n              items: [],\n            },\n          ],\n        },\n      ];\n\n      const arr2: MergedQualityProfileQualityItemResource[] = [\n        {\n          allowed: true,\n          id: 1000,\n          name: \"Merged QPs\",\n          items: [\n            {\n              quality: {\n                id: 14,\n                name: \"WEBRip-720p\",\n                resolution: 720,\n                source: \"webrip\",\n              },\n              allowed: true,\n              items: [],\n            },\n          ],\n        },\n      ];\n\n      expect(isOrderOfQualitiesEqual(arr1, arr2)).toBe(true);\n    });\n  });\n\n  describe(\"mapQualityProfiles - use_default_score flag\", () => {\n    test(\"should use default score when use_default_score is true\", async () => {\n      // Setup CF with default score of 25\n      const carrIdMapping = new Map([\n        [\n          \"test-cf-id\",\n          {\n            carrConfig: {\n              configarr_id: \"test-cf-id\",\n              name: \"Test CF\",\n              configarr_scores: { default: 25 },\n            },\n            requestConfig: {},\n          },\n        ],\n      ]);\n\n      const cfMap: CFProcessing = {\n        carrIdMapping,\n        cfNameToCarrConfig: new Map(),\n      };\n\n      const config: MergedConfigInstance = {\n        custom_formats: [\n          {\n            trash_ids: [\"test-cf-id\"],\n            assign_scores_to: [{ name: \"profile\", use_default_score: true }],\n          },\n        ],\n        quality_profiles: [\n          {\n            name: \"profile\",\n            min_format_score: 0,\n            qualities: [],\n            quality_sort: \"top\",\n            upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n            score_set: \"default\",\n          },\n        ],\n        customFormatDefinitions: [],\n        media_management: {},\n        media_naming: {},\n      };\n\n      const result = mapQualityProfiles(cfMap, config);\n      const profileScore = result.get(\"profile\");\n      const cfScore = profileScore?.get(\"Test CF\");\n\n      expect(cfScore?.score).toBe(25); // Should use default score\n    });\n\n    test(\"should use explicit score when use_default_score is false or not set\", async () => {\n      const carrIdMapping = new Map([\n        [\n          \"test-cf-id\",\n          {\n            carrConfig: {\n              configarr_id: \"test-cf-id\",\n              name: \"Test CF\",\n              configarr_scores: { default: 25 },\n            },\n            requestConfig: {},\n          },\n        ],\n      ]);\n\n      const cfMap: CFProcessing = {\n        carrIdMapping,\n        cfNameToCarrConfig: new Map(),\n      };\n\n      const config: MergedConfigInstance = {\n        custom_formats: [\n          {\n            trash_ids: [\"test-cf-id\"],\n            assign_scores_to: [{ name: \"profile\", score: 100 }],\n          },\n        ],\n        quality_profiles: [\n          {\n            name: \"profile\",\n            min_format_score: 0,\n            qualities: [],\n            quality_sort: \"top\",\n            upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n            score_set: \"default\",\n          },\n        ],\n        customFormatDefinitions: [],\n        media_management: {},\n        media_naming: {},\n      };\n\n      const result = mapQualityProfiles(cfMap, config);\n      const profileScore = result.get(\"profile\");\n      const cfScore = profileScore?.get(\"Test CF\");\n\n      expect(cfScore?.score).toBe(100); // Should use explicit score\n    });\n\n    test(\"should prefer use_default_score over explicit score when both are set\", async () => {\n      // When both use_default_score: true and score are set, use_default_score takes precedence\n      const carrIdMapping = new Map([\n        [\n          \"test-cf-id\",\n          {\n            carrConfig: {\n              configarr_id: \"test-cf-id\",\n              name: \"Test CF\",\n              configarr_scores: { default: 25 },\n            },\n            requestConfig: {},\n          },\n        ],\n      ]);\n\n      const cfMap: CFProcessing = {\n        carrIdMapping,\n        cfNameToCarrConfig: new Map(),\n      };\n\n      const config: MergedConfigInstance = {\n        custom_formats: [\n          {\n            trash_ids: [\"test-cf-id\"],\n            assign_scores_to: [{ name: \"profile\", score: 100, use_default_score: true }],\n          },\n        ],\n        quality_profiles: [\n          {\n            name: \"profile\",\n            min_format_score: 0,\n            qualities: [],\n            quality_sort: \"top\",\n            upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n            score_set: \"default\",\n          },\n        ],\n        customFormatDefinitions: [],\n        media_management: {},\n        media_naming: {},\n      };\n\n      const result = mapQualityProfiles(cfMap, config);\n      const profileScore = result.get(\"profile\");\n      const cfScore = profileScore?.get(\"Test CF\");\n\n      expect(cfScore?.score).toBe(25); // Should use default score, ignoring explicit 100\n    });\n\n    test(\"should use default when no score and no use_default_score\", async () => {\n      const carrIdMapping = new Map([\n        [\n          \"test-cf-id\",\n          {\n            carrConfig: {\n              configarr_id: \"test-cf-id\",\n              name: \"Test CF\",\n              configarr_scores: { default: 25 },\n            },\n            requestConfig: {},\n          },\n        ],\n      ]);\n\n      const cfMap: CFProcessing = {\n        carrIdMapping,\n        cfNameToCarrConfig: new Map(),\n      };\n\n      const config: MergedConfigInstance = {\n        custom_formats: [\n          {\n            trash_ids: [\"test-cf-id\"],\n            assign_scores_to: [{ name: \"profile\" }], // No score, no flag\n          },\n        ],\n        quality_profiles: [\n          {\n            name: \"profile\",\n            min_format_score: 0,\n            qualities: [],\n            quality_sort: \"top\",\n            upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n            score_set: \"default\",\n          },\n        ],\n        customFormatDefinitions: [],\n        media_management: {},\n        media_naming: {},\n      };\n\n      const result = mapQualityProfiles(cfMap, config);\n      const profileScore = result.get(\"profile\");\n      const cfScore = profileScore?.get(\"Test CF\");\n\n      expect(cfScore?.score).toBe(25); // Should fall back to default\n    });\n\n    test(\"should use score_set when configured and no explicit score\", async () => {\n      const carrIdMapping = new Map([\n        [\n          \"test-cf-id\",\n          {\n            carrConfig: {\n              configarr_id: \"test-cf-id\",\n              name: \"Test CF\",\n              configarr_scores: { default: 25, \"anime-sonarr\": 50 },\n            },\n            requestConfig: {},\n          },\n        ],\n      ]);\n\n      const cfMap: CFProcessing = {\n        carrIdMapping,\n        cfNameToCarrConfig: new Map(),\n      };\n\n      const config: MergedConfigInstance = {\n        custom_formats: [\n          {\n            trash_ids: [\"test-cf-id\"],\n            assign_scores_to: [{ name: \"profile\" }], // No score\n          },\n        ],\n        quality_profiles: [\n          {\n            name: \"profile\",\n            min_format_score: 0,\n            qualities: [],\n            quality_sort: \"top\",\n            upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n            score_set: \"anime-sonarr\", // Use anime-sonarr score set\n          },\n        ],\n        customFormatDefinitions: [],\n        media_management: {},\n        media_naming: {},\n      };\n\n      const result = mapQualityProfiles(cfMap, config);\n      const profileScore = result.get(\"profile\");\n      const cfScore = profileScore?.get(\"Test CF\");\n\n      expect(cfScore?.score).toBe(50); // Should use score_set (anime-sonarr) score\n    });\n\n    test(\"use_default_score should ignore score_set\", async () => {\n      const carrIdMapping = new Map([\n        [\n          \"test-cf-id\",\n          {\n            carrConfig: {\n              configarr_id: \"test-cf-id\",\n              name: \"Test CF\",\n              configarr_scores: { default: 25, \"anime-sonarr\": 50 },\n            },\n            requestConfig: {},\n          },\n        ],\n      ]);\n\n      const cfMap: CFProcessing = {\n        carrIdMapping,\n        cfNameToCarrConfig: new Map(),\n      };\n\n      const config: MergedConfigInstance = {\n        custom_formats: [\n          {\n            trash_ids: [\"test-cf-id\"],\n            assign_scores_to: [{ name: \"profile\", use_default_score: true }],\n          },\n        ],\n        quality_profiles: [\n          {\n            name: \"profile\",\n            min_format_score: 0,\n            qualities: [],\n            quality_sort: \"top\",\n            upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n            score_set: \"anime-sonarr\",\n          },\n        ],\n        customFormatDefinitions: [],\n        media_management: {},\n        media_naming: {},\n      };\n\n      const result = mapQualityProfiles(cfMap, config);\n      const profileScore = result.get(\"profile\");\n      const cfScore = profileScore?.get(\"Test CF\");\n\n      expect(cfScore?.score).toBe(25); // Should use default, ignoring score_set\n    });\n  });\n\n  describe(\"checkForConflictingCFs\", () => {\n    beforeEach(() => {\n      vi.restoreAllMocks();\n    });\n\n    test(\"should warn when same quality profile contains 2 conflicting ids from one group\", () => {\n      const carrIdMapping = new Map([\n        [\n          \"cf1-id\",\n          {\n            carrConfig: {\n              configarr_id: \"cf1-id\",\n              name: \"SDR\",\n            },\n            requestConfig: {},\n          },\n        ],\n        [\n          \"cf2-id\",\n          {\n            carrConfig: {\n              configarr_id: \"cf2-id\",\n              name: \"SDR (no WEBDL)\",\n            },\n            requestConfig: {},\n          },\n        ],\n      ]);\n\n      const cfMap: CFProcessing = {\n        carrIdMapping,\n        cfNameToCarrConfig: new Map(),\n      };\n\n      const config: MergedConfigInstance = {\n        quality_profiles: [\n          {\n            name: \"profile1\",\n            min_format_score: 0,\n            qualities: [],\n            quality_sort: \"top\",\n            upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n            score_set: \"default\",\n          },\n        ],\n        custom_formats: [\n          {\n            trash_ids: [\"cf1-id\", \"cf2-id\"],\n            assign_scores_to: [{ name: \"profile1\" }],\n          },\n        ],\n        customFormatDefinitions: [],\n        media_management: {},\n        media_naming: {},\n      };\n\n      const conflicts = [\n        {\n          trash_id: \"sdr-conflict\",\n          name: \"SDR Conflict\",\n          custom_formats: [\n            { trash_id: \"cf1-id\", name: \"SDR\" },\n            { trash_id: \"cf2-id\", name: \"SDR (no WEBDL)\" },\n          ],\n        },\n      ];\n\n      const logSpy = vi.spyOn(log.logger, \"warn\").mockImplementation(() => {});\n\n      checkForConflictingCFs(cfMap, config, conflicts);\n\n      expect(logSpy).toHaveBeenCalledTimes(1);\n      expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(\"QualityProfile \\'profile1\\'\"));\n      expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(\"Conflicting CustomFormats detected [SDR, SDR (no WEBDL)]\"));\n      expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(\"conflict group \\'SDR Conflict\\'\"));\n    });\n\n    test(\"should not warn when conflicting ids are split across different quality profiles\", () => {\n      const carrIdMapping = new Map([\n        [\n          \"cf1-id\",\n          {\n            carrConfig: {\n              configarr_id: \"cf1-id\",\n              name: \"SDR\",\n            },\n            requestConfig: {},\n          },\n        ],\n        [\n          \"cf2-id\",\n          {\n            carrConfig: {\n              configarr_id: \"cf2-id\",\n              name: \"SDR (no WEBDL)\",\n            },\n            requestConfig: {},\n          },\n        ],\n      ]);\n\n      const cfMap: CFProcessing = {\n        carrIdMapping,\n        cfNameToCarrConfig: new Map(),\n      };\n\n      const config: MergedConfigInstance = {\n        quality_profiles: [\n          {\n            name: \"profile1\",\n            min_format_score: 0,\n            qualities: [],\n            quality_sort: \"top\",\n            upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n            score_set: \"default\",\n          },\n          {\n            name: \"profile2\",\n            min_format_score: 0,\n            qualities: [],\n            quality_sort: \"top\",\n            upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n            score_set: \"default\",\n          },\n        ],\n        custom_formats: [\n          {\n            trash_ids: [\"cf1-id\"],\n            assign_scores_to: [{ name: \"profile1\" }],\n          },\n          {\n            trash_ids: [\"cf2-id\"],\n            assign_scores_to: [{ name: \"profile2\" }],\n          },\n        ],\n        customFormatDefinitions: [],\n        media_management: {},\n        media_naming: {},\n      };\n\n      const conflicts = [\n        {\n          trash_id: \"sdr-conflict\",\n          name: \"SDR Conflict\",\n          custom_formats: [\n            { trash_id: \"cf1-id\", name: \"SDR\" },\n            { trash_id: \"cf2-id\", name: \"SDR (no WEBDL)\" },\n          ],\n        },\n      ];\n\n      const logSpy = vi.spyOn(log.logger, \"warn\").mockImplementation(() => {});\n\n      checkForConflictingCFs(cfMap, config, conflicts);\n\n      expect(logSpy).not.toHaveBeenCalled();\n    });\n\n    test(\"should not warn when only one id from group is selected\", () => {\n      const carrIdMapping = new Map([\n        [\n          \"cf1-id\",\n          {\n            carrConfig: {\n              configarr_id: \"cf1-id\",\n              name: \"SDR\",\n            },\n            requestConfig: {},\n          },\n        ],\n      ]);\n\n      const cfMap: CFProcessing = {\n        carrIdMapping,\n        cfNameToCarrConfig: new Map(),\n      };\n\n      const config: MergedConfigInstance = {\n        quality_profiles: [\n          {\n            name: \"profile1\",\n            min_format_score: 0,\n            qualities: [],\n            quality_sort: \"top\",\n            upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n            score_set: \"default\",\n          },\n        ],\n        custom_formats: [\n          {\n            trash_ids: [\"cf1-id\"],\n            assign_scores_to: [{ name: \"profile1\" }],\n          },\n        ],\n        customFormatDefinitions: [],\n        media_management: {},\n        media_naming: {},\n      };\n\n      const conflicts = [\n        {\n          trash_id: \"sdr-conflict\",\n          name: \"SDR Conflict\",\n          custom_formats: [\n            { trash_id: \"cf1-id\", name: \"SDR\" },\n            { trash_id: \"cf2-id\", name: \"SDR (no WEBDL)\" },\n          ],\n        },\n      ];\n\n      const logSpy = vi.spyOn(log.logger, \"warn\").mockImplementation(() => {});\n\n      checkForConflictingCFs(cfMap, config, conflicts);\n\n      expect(logSpy).not.toHaveBeenCalled();\n    });\n\n    test(\"warning uses fallback name/id if lookup data incomplete\", () => {\n      const carrIdMapping = new Map(); // Empty - no name lookup\n      const cfMap: CFProcessing = {\n        carrIdMapping,\n        cfNameToCarrConfig: new Map(),\n      };\n\n      const config: MergedConfigInstance = {\n        quality_profiles: [\n          {\n            name: \"profile1\",\n            min_format_score: 0,\n            qualities: [],\n            quality_sort: \"top\",\n            upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n            score_set: \"default\",\n          },\n        ],\n        custom_formats: [\n          {\n            trash_ids: [\"cf1-id\", \"cf2-id\"],\n            assign_scores_to: [{ name: \"profile1\" }],\n          },\n        ],\n        customFormatDefinitions: [],\n        media_management: {},\n        media_naming: {},\n      };\n\n      const conflicts = [\n        {\n          trash_id: \"sdr-conflict\",\n          name: \"SDR Conflict\",\n          custom_formats: [\n            { trash_id: \"cf1-id\", name: \"SDR Override\" },\n            { trash_id: \"cf2-id\", name: \"SDR (no WEBDL)\" },\n          ],\n        },\n      ];\n\n      const logSpy = vi.spyOn(log.logger, \"warn\").mockImplementation(() => {});\n\n      checkForConflictingCFs(cfMap, config, conflicts);\n\n      expect(logSpy).toHaveBeenCalledTimes(1);\n      expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(\"[SDR Override, SDR (no WEBDL)]\"));\n      expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(\"(ids: [cf1-id, cf2-id])\"));\n    });\n\n    test(\"should return early if conflicts list is empty\", () => {\n      const carrIdMapping = new Map();\n      const cfMap: CFProcessing = {\n        carrIdMapping,\n        cfNameToCarrConfig: new Map(),\n      };\n\n      const config: MergedConfigInstance = {\n        quality_profiles: [\n          {\n            name: \"profile1\",\n            min_format_score: 0,\n            qualities: [],\n            quality_sort: \"top\",\n            upgrade: { allowed: true, until_quality: \"HDTV-1080p\", until_score: 1000 },\n            score_set: \"default\",\n          },\n        ],\n        custom_formats: [\n          {\n            trash_ids: [\"cf1-id\", \"cf2-id\"],\n            assign_scores_to: [{ name: \"profile1\" }],\n          },\n        ],\n        customFormatDefinitions: [],\n        media_management: {},\n        media_naming: {},\n      };\n\n      const logSpy = vi.spyOn(log.logger, \"warn\").mockImplementation(() => {});\n\n      checkForConflictingCFs(cfMap, config, []);\n      checkForConflictingCFs(cfMap, config, undefined as any);\n\n      expect(logSpy).not.toHaveBeenCalled();\n    });\n  });\n});\n"
  },
  {
    "path": "src/quality-profiles.ts",
    "content": "import path from \"node:path\";\nimport {\n  MergedCustomFormatResource,\n  MergedProfileFormatItemResource,\n  MergedQualityDefinitionResource,\n  MergedQualityProfileQualityItemResource,\n  MergedQualityProfileResource,\n} from \"./types/merged.types\";\nimport { ServerCache } from \"./cache\";\nimport { ArrClientLanguageResource, getUnifiedClient } from \"./clients/unified-client\";\nimport { getEnvs } from \"./env\";\nimport { logger } from \"./logger\";\nimport { ArrType, CFProcessing } from \"./types/common.types\";\nimport { ConfigQualityProfile, ConfigQualityProfileItem, MergedConfigInstance } from \"./types/config.types\";\nimport type { TrashCFConflict } from \"./types/trashguide.types\";\nimport { cloneWithJSON, loadJsonFile, notEmpty, zip } from \"./util\";\n\nexport const deleteAllQualityProfiles = async () => {\n  const api = getUnifiedClient();\n  const qualityProfilesOnServer = await api.getQualityProfiles();\n\n  for (const qualityProfile of qualityProfilesOnServer) {\n    await api.deleteQualityProfile(qualityProfile.id + \"\");\n    logger.info(`Deleted QP: '${qualityProfile.name}'`);\n  }\n};\n\nexport const deleteQualityProfile = async (qualityProfile: MergedQualityProfileResource) => {\n  const api = getUnifiedClient();\n\n  await api.deleteQualityProfile(qualityProfile.id + \"\");\n  logger.info(`Deleted QP: '${qualityProfile.name || qualityProfile.id}'`);\n};\n\n// merge CFs of templates and custom CFs into one mapping of QualityProfile -> CFs + Score\nexport const mapQualityProfiles = ({ carrIdMapping }: CFProcessing, { custom_formats, quality_profiles }: MergedConfigInstance) => {\n  // QualityProfile -> (CF Name -> Scoring)\n  const profileScores = new Map<string, Map<string, MergedProfileFormatItemResource>>();\n\n  const defaultScoringMap = new Map(quality_profiles.map((obj) => [obj.name, obj]));\n\n  for (const { trash_ids, assign_scores_to } of custom_formats) {\n    if (!trash_ids || !assign_scores_to) {\n      continue;\n    }\n\n    for (const profile of assign_scores_to) {\n      for (const trashId of trash_ids) {\n        const carr = carrIdMapping.get(trashId);\n\n        if (!carr) {\n          logger.warn(`Unknown ID for CF. ${trashId}`);\n          continue;\n        }\n\n        let selectedProfileMap = profileScores.get(profile.name);\n\n        if (!selectedProfileMap) {\n          const newMap = new Map();\n          profileScores.set(profile.name, newMap);\n          selectedProfileMap = newMap;\n        }\n\n        let cfScore = selectedProfileMap.get(carr.carrConfig.name!);\n\n        if (!cfScore) {\n          const newScore = {};\n          selectedProfileMap.set(carr.carrConfig.name!, newScore);\n          cfScore = newScore;\n        }\n\n        cfScore.name = carr.carrConfig.name;\n\n        const profileScoreConfig = defaultScoringMap.get(profile.name);\n\n        let score_set: number | undefined;\n\n        if (profileScoreConfig && profileScoreConfig.score_set) {\n          score_set = carr.carrConfig.configarr_scores?.[profileScoreConfig.score_set];\n        }\n\n        // If use_default_score is explicitly set to true, use the TRaSH Guide default score\n        // This overrides any explicit score set by groups or templates\n        if (profile.use_default_score === true) {\n          cfScore.score = carr.carrConfig.configarr_scores?.default;\n        } else {\n          // Normal score resolution: explicit score > score_set > default\n          cfScore.score = profile.score ?? score_set ?? carr.carrConfig.configarr_scores?.default;\n        }\n      }\n    }\n  }\n\n  return profileScores;\n};\n\nexport const loadQualityProfilesFromServer = async (): Promise<MergedQualityProfileResource[]> => {\n  if (getEnvs().LOAD_LOCAL_SAMPLES) {\n    return loadJsonFile(path.resolve(__dirname, `../tests/samples/quality_profiles.json`));\n  }\n  const api = getUnifiedClient();\n\n  const qualityProfiles = await api.getQualityProfiles();\n  // TODO type hack\n  return qualityProfiles as MergedQualityDefinitionResource[];\n};\n\n// TODO should we use clones or not?\nexport const mapQualities = (qd_source: MergedQualityDefinitionResource[], value_source: ConfigQualityProfile) => {\n  const qd = cloneWithJSON(qd_source);\n  const value = cloneWithJSON(value_source);\n\n  const qdMap = new Map(qd.map((obj) => [obj.quality?.name, obj]));\n  const qdLookupWithTitle = new Map(qdMap);\n\n  // Add title names to map and overwrite existing ones if they exist. This acts as fallback if someone used the title we do not want to keep it user friendly.\n  qdLookupWithTitle.forEach((element) => {\n    if (element.title) {\n      qdLookupWithTitle.set(element.title, element);\n    }\n  });\n\n  const allowedQualities = value.qualities.map<MergedQualityProfileQualityItemResource>((obj, i) => {\n    if (obj.qualities?.length && obj.qualities.length > 0) {\n      return {\n        allowed: obj.enabled ?? true,\n        id: 1000 + i,\n        name: obj.name,\n        items:\n          obj.qualities\n            ?.map<MergedQualityProfileQualityItemResource>((obj2) => {\n              const qd = qdLookupWithTitle.get(obj2);\n\n              const returnObject: MergedQualityProfileQualityItemResource = {\n                quality: {\n                  id: qd?.quality?.id,\n                  name: obj2,\n                  resolution: qd?.quality?.resolution,\n                  source: qd?.quality?.source,\n                },\n                allowed: obj.enabled ?? true,\n                items: [],\n              };\n\n              qdMap.delete(qd?.quality?.name);\n\n              return returnObject;\n            })\n            .reverse() || [],\n      };\n    } else {\n      const serverQD = qdLookupWithTitle.get(obj.name);\n\n      if (serverQD == null) {\n        logger.warn(`Unknown requested quality \"${obj.name}\" for quality profile ${value.name}`);\n        throw new Error(`Please correct your config.`);\n      }\n\n      qdMap.delete(serverQD.quality?.name);\n\n      const item: MergedQualityProfileQualityItemResource = {\n        allowed: obj.enabled ?? true,\n        items: [],\n        quality: {\n          ...serverQD?.quality,\n        },\n      };\n      return item;\n    }\n  });\n\n  const missingQualities: MergedQualityProfileQualityItemResource[] = [];\n\n  for (const [key, value] of qdMap.entries()) {\n    missingQualities.push({\n      allowed: false,\n      items: [],\n      //id: qualIndex++, // ID not allowed if not enabled\n      quality: {\n        id: value.quality?.id,\n        name: key,\n        resolution: value.quality?.resolution,\n        source: value?.quality?.source,\n      },\n    });\n  }\n\n  // Ordering of items in the array matters of how they will be displayed. First is last.\n  // Need to double check if always works as expected also regarding of templates etc.\n\n  // TODO no sure if a useful feature\n  if (value.quality_sort === \"bottom\") {\n    return [...allowedQualities.reverse(), ...missingQualities];\n  } else {\n    // default = top\n    return [...missingQualities, ...allowedQualities.reverse()];\n  }\n};\n\nexport const isOrderOfQualitiesEqual = (\n  arr1: MergedQualityProfileQualityItemResource[],\n  arr2: MergedQualityProfileQualityItemResource[],\n) => {\n  if (arr1.length !== arr2.length) {\n    return false;\n  }\n\n  for (const [element1, element2] of zip(arr1, arr2)) {\n    if (element1.name !== element2.name) {\n      return false;\n    }\n\n    if (element1.quality?.name !== element2.quality?.name) {\n      return false;\n    }\n\n    const items1 = element1.items ?? [];\n    const item2s = element2.items ?? [];\n\n    if (!(items1.length === 0 && items1.length === item2s.length)) {\n      if (!isOrderOfQualitiesEqual(element1.items ?? [], element2.items ?? [])) {\n        return false;\n      }\n    }\n  }\n\n  return true;\n};\n\n/**\n * Method to check if the order of qualities in the configuration syntax is equals.\n * Does not check nested qualities!\n * @deprecated\n * @param obj1\n * @param obj2\n * @returns\n */\nexport const isOrderOfConfigQualitiesEqual = (obj1: ConfigQualityProfileItem[], obj2: ConfigQualityProfileItem[]) => {\n  if (obj1.length !== obj2.length) {\n    return false;\n  }\n\n  for (const [element1, element2] of zip(obj1, obj2)) {\n    if (element1.name !== element2.name) {\n      return false;\n    }\n  }\n\n  return true;\n};\n\n/**\n * TODO: time to split into arr specifc executions\n * Idea probably to have a common method which will be called by arr specific methods or do generic and call afterwards the specific.\n * Like: doGeneric -> if sonarr doSonarr\n *\n * @param arrType\n * @param cfMap\n * @param config\n * @param serverCache\n * @returns\n */\n\n/**\n * Resolves the cutoff ID to use when upgrades are disabled.\n * Prefers `untilQuality` when provided; falls back to the highest-priority\n * allowed quality in the mapped list. Throws when no allowed quality exists.\n */\nconst getDisabledUpgradeCutoff = (\n  mappedQualities: MergedQualityProfileQualityItemResource[],\n  qualityToId: Map<string, number>,\n  untilQuality: string | undefined,\n  profileName: string,\n): number => {\n  if (untilQuality != null) {\n    const id = qualityToId.get(untilQuality);\n    if (id != null) return id;\n  }\n\n  // mappedQualities is in API order (reversed from config), so the last item is\n  // the highest-priority quality in user config.\n  const fallback = mappedQualities\n    .slice()\n    .reverse()\n    .find((e) => e.allowed);\n  const fallbackId = fallback?.id ?? fallback?.quality?.id;\n\n  if (fallbackId == null) {\n    throw new Error(`QualityProfile '${profileName}': no allowed quality found to use as cutoff when upgrade is disabled`);\n  }\n\n  if (untilQuality == null) {\n    logger.debug(\n      `QualityProfile '${profileName}': upgrade.until_quality not specified; using highest-priority allowed quality as cutoff (id=${fallbackId})`,\n    );\n  }\n\n  return fallbackId;\n};\n\nexport const calculateQualityProfilesDiff = async (\n  arrType: ArrType,\n  cfMap: CFProcessing,\n  config: MergedConfigInstance,\n  serverCache: ServerCache,\n): Promise<{\n  changedQPs: MergedQualityProfileResource[];\n  create: MergedQualityProfileResource[];\n  noChanges: string[];\n}> => {\n  // TODO maybe improve?\n  const scoring = mapQualityProfiles(cfMap, config);\n  const qpMerged = new Map(config.quality_profiles.map((obj) => [obj.name, obj]));\n  const qpServerMap = new Map(serverCache.qp.map((obj) => [obj.name!, obj]));\n  const cfServerMap = new Map(serverCache.cf.map((obj) => [obj.name!, obj]));\n  const languageMap = new Map(serverCache.languages.map((obj) => [obj.name!, obj]));\n\n  const createQPs: MergedQualityProfileResource[] = [];\n  const changedQPs: MergedQualityProfileResource[] = [];\n  const noChangedQPs: string[] = [];\n\n  const changes = new Map<string, string[]>();\n\n  for (const [name, value] of qpMerged.entries()) {\n    const serverMatch = qpServerMap.get(name);\n    const scoringForQP = scoring.get(name);\n    const mappedQualities = mapQualities(serverCache.qd, value);\n\n    let profileLanguage: ArrClientLanguageResource | undefined;\n\n    if (value.language) {\n      profileLanguage = languageMap.get(value.language);\n\n      if (profileLanguage == null) {\n        logger.warn(`Profile language '${value.language}' not found in server. Ignoring.`);\n        // profileLanguage = languageMap.get(\"Any\");\n      }\n    }\n\n    const resetScoreExceptions: Map<string, boolean> =\n      value.reset_unmatched_scores?.except?.reduce((p, c) => {\n        p.set(c, true);\n        return p;\n      }, new Map()) ?? new Map();\n\n    if (serverMatch == null) {\n      logger.info(`QualityProfile '${name}' not found in server. Will be created.`);\n\n      const qualityToId = mappedQualities.reduce<Map<string, number>>((p, c) => {\n        const id = c.id ?? c.quality?.id;\n        const qName = c.name ?? c.quality?.name;\n\n        if (id == null || qName == null) {\n          throw new Error(`No ID (${id}) or name ${qName} found for quality? QP: ${name}`);\n        }\n\n        p.set(qName, id);\n\n        return p;\n      }, new Map());\n\n      const cfs: Map<string, MergedCustomFormatResource> = new Map(JSON.parse(JSON.stringify(Array.from(cfServerMap))));\n\n      const customFormatsMapped = Array.from(cfs.values()).map<MergedProfileFormatItemResource>((e) => {\n        let score = 0;\n\n        if (scoringForQP) {\n          const providedScore = scoringForQP.get(e.name!);\n          score = providedScore?.score || 0;\n        }\n\n        return {\n          name: e.name,\n          score: score,\n          format: e.id,\n        };\n      });\n\n      let newP: MergedQualityProfileResource = {\n        name: value.name,\n        items: mappedQualities,\n        minFormatScore: value.min_format_score,\n        formatItems: customFormatsMapped,\n      };\n\n      if (value.upgrade.allowed) {\n        if (value.upgrade.until_quality == null) {\n          throw new Error(`QualityProfile '${name}': upgrade.until_quality is required when upgrade.allowed is true`);\n        }\n\n        Object.assign<MergedQualityProfileResource, MergedQualityProfileResource | null | undefined>(newP, {\n          cutoff: qualityToId.get(value.upgrade.until_quality),\n          cutoffFormatScore: value.upgrade.until_score,\n          upgradeAllowed: true,\n          minUpgradeFormatScore: value.upgrade.min_format_score ?? 1,\n        });\n      } else {\n        const cutoffId = getDisabledUpgradeCutoff(mappedQualities, qualityToId, value.upgrade.until_quality, name);\n\n        Object.assign<MergedQualityProfileResource, MergedQualityProfileResource | null | undefined>(newP, {\n          cutoff: cutoffId,\n          cutoffFormatScore: 1,\n          minUpgradeFormatScore: 1,\n          upgradeAllowed: false,\n        });\n      }\n\n      Object.assign<MergedQualityProfileResource, MergedQualityProfileResource | null | undefined>(\n        newP,\n        profileLanguage && { language: profileLanguage }, // TODO split out. Not exists for sonarr\n      );\n      const newProfile = newP;\n      createQPs.push(newProfile);\n      continue;\n    }\n\n    const changeList: string[] = [];\n    changes.set(serverMatch.name!, changeList);\n\n    const updatedServerObject: MergedQualityProfileResource = JSON.parse(JSON.stringify(serverMatch));\n\n    let diffExist = false;\n\n    // TODO do we want to enforce the whole structure or only match those which are enabled by us?\n    if (!isOrderOfQualitiesEqual(mappedQualities, serverMatch.items || [])) {\n      logger.debug(`QualityProfile quality order mismatch.`);\n      diffExist = true;\n\n      changeList.push(`QualityProfile quality order does not match`);\n      updatedServerObject.items = mappedQualities;\n    }\n\n    const qualityToId = updatedServerObject.items!.reduce<Map<string, number>>((p, c) => {\n      const id = c.id ?? c.quality?.id;\n      const qName = c.name ?? c.quality?.name;\n\n      if (id == null || qName == null) {\n        throw new Error(`No ID (${id}) or name ${qName} found for quality? QP: ${name}`);\n      }\n\n      p.set(qName, id);\n\n      return p;\n    }, new Map());\n\n    if (value.min_format_score != null) {\n      if (serverMatch.minFormatScore !== value.min_format_score) {\n        updatedServerObject.minFormatScore = value.min_format_score;\n        diffExist = true;\n        changeList.push(`MinFormatScore diff: server: ${serverMatch.minFormatScore} - expected: ${value.min_format_score}`);\n      }\n    }\n\n    if (value.upgrade != null) {\n      if (serverMatch.upgradeAllowed !== value.upgrade.allowed) {\n        updatedServerObject.upgradeAllowed = value.upgrade.allowed;\n        diffExist = true;\n\n        changeList.push(`UpgradeAllowed diff: server: ${serverMatch.upgradeAllowed} - expected: ${value.upgrade.allowed}`);\n      }\n\n      // Further diffs only necessary if upgrade is allowed\n      if (value.upgrade.allowed) {\n        if (value.upgrade.until_quality == null) {\n          throw new Error(`QualityProfile '${name}': upgrade.until_quality is required when upgrade.allowed is true`);\n        }\n\n        const upgradeUntil = qualityToId.get(value.upgrade.until_quality);\n\n        if (upgradeUntil == null) {\n          throw new Error(`Did not find expected Quality to upgrade until: ${value.upgrade.until_quality}`);\n        }\n\n        if (serverMatch.cutoff !== upgradeUntil) {\n          updatedServerObject.cutoff = upgradeUntil;\n          diffExist = true;\n          changeList.push(`Upgrade until quality diff: server: ${serverMatch.cutoff} - expected: ${upgradeUntil}`);\n        }\n\n        if (serverMatch.cutoffFormatScore !== value.upgrade.until_score) {\n          updatedServerObject.cutoffFormatScore = value.upgrade.until_score;\n          diffExist = true;\n\n          changeList.push(`Upgrade until score diff: server: ${serverMatch.cutoffFormatScore} - expected: ${value.upgrade.until_score}`);\n        }\n\n        const configMinUpgradeFormatScore = value.upgrade.min_format_score ?? 1;\n\n        // if not configured ignore\n        if (value.upgrade.min_format_score != null && serverMatch.minUpgradeFormatScore !== configMinUpgradeFormatScore) {\n          updatedServerObject.minUpgradeFormatScore = configMinUpgradeFormatScore;\n          diffExist = true;\n\n          changeList.push(\n            `Min upgrade format score diff: server: ${serverMatch.cutoffFormatScore} - expected: ${configMinUpgradeFormatScore}`,\n          );\n        }\n      } else {\n        const cutoffId = getDisabledUpgradeCutoff(mappedQualities, qualityToId, value.upgrade.until_quality, name);\n\n        if (serverMatch.cutoff !== cutoffId) {\n          updatedServerObject.cutoff = cutoffId;\n          diffExist = true;\n          changeList.push(`Cutoff diff for upgrade disabled: server: ${serverMatch.cutoff} - expected: ${cutoffId}`);\n        }\n\n        if (serverMatch.cutoffFormatScore !== 1) {\n          updatedServerObject.cutoffFormatScore = 1;\n          diffExist = true;\n          changeList.push(`CutoffFormatScore diff for upgrade disabled: server: ${serverMatch.cutoffFormatScore} - expected: 1`);\n        }\n\n        if (serverMatch.minUpgradeFormatScore !== 1) {\n          updatedServerObject.minUpgradeFormatScore = 1;\n          diffExist = true;\n          changeList.push(`MinUpgradeFormatScore diff for upgrade disabled: server: ${serverMatch.minUpgradeFormatScore} - expected: 1`);\n        }\n      }\n    }\n\n    if (profileLanguage != null && serverMatch.language?.name !== profileLanguage.name) {\n      updatedServerObject.language = profileLanguage;\n      diffExist = true;\n      changeList.push(`Language diff: server: ${serverMatch.language?.name} - expected: ${profileLanguage?.name}`);\n    }\n\n    // CFs matching. Hint: make sure to execute the method with updated CFs. Otherwise if we create CFs and update existing profiles those could be missing.\n    const serverProfileCFMap = new Map(serverMatch.formatItems!.map((obj) => [obj.name!, obj]));\n\n    let scoringDiff = false;\n\n    if (scoringForQP != null) {\n      const newCFFormats: MergedProfileFormatItemResource[] = [];\n\n      for (const [scoreKey, scoreValue] of scoringForQP.entries()) {\n        const serverCF = serverProfileCFMap.get(scoreKey);\n        serverProfileCFMap.delete(scoreKey);\n\n        // TODO (1): check where best handled\n        if (scoreValue.score == null) {\n          if (value.reset_unmatched_scores?.enabled && !resetScoreExceptions.has(scoreKey) && serverCF?.score !== 0) {\n            scoringDiff = true;\n            changeList.push(`CF resetting score '${scoreValue.name}': server ${serverCF?.score} - client: 0`);\n            newCFFormats.push({ ...serverCF, score: 0 });\n          } else {\n            newCFFormats.push({ ...serverCF });\n          }\n        } else {\n          if (serverCF?.score !== scoreValue.score) {\n            scoringDiff = true;\n            changeList.push(`CF diff ${scoreValue.name}: server: ${serverCF?.score} - expected: ${scoreValue.score}`);\n            newCFFormats.push({ ...serverCF, score: scoreValue.score });\n          } else {\n            newCFFormats.push({ ...serverCF });\n          }\n        }\n      }\n\n      const missingCfs = Array.from(serverProfileCFMap.values()).reduce<MergedProfileFormatItemResource[]>((p, c) => {\n        const cfName = c.name!;\n        const cfScore = c.score;\n\n        if (value.reset_unmatched_scores?.enabled && !resetScoreExceptions.has(c.name!) && cfScore !== 0) {\n          scoringDiff = true;\n          changeList.push(`CF resetting score '${cfName}': server ${cfScore} - client: 0`);\n          p.push({ ...c, score: 0 });\n        } else {\n          p.push(c);\n        }\n\n        return p;\n      }, []);\n\n      newCFFormats.push(...missingCfs);\n\n      updatedServerObject.formatItems = newCFFormats;\n    } else {\n      logger.info(`No scoring for QualityProfile '${serverMatch.name!}' found`);\n    }\n\n    logger.debug(\n      `QualityProfile (${value.name}) - In Sync: ${changeList.length <= 0}, CF Changes: ${scoringDiff}, Some other diff: ${diffExist}`,\n    );\n\n    if (scoringDiff || diffExist) {\n      changedQPs.push(updatedServerObject);\n    } else {\n      noChangedQPs.push(value.name);\n    }\n\n    if (changeList.length > 0) {\n      logger.debug(changeList, `ChangeList for QualityProfile '${value.name}'`);\n    }\n  }\n\n  const serverQpsUnmanaged = getUnmanagedQualityProfiles(serverCache.qp, config.quality_profiles);\n\n  if (serverQpsUnmanaged.length > 0) {\n    logger.debug(\n      `Found existing ${serverQpsUnmanaged.length} QualityProfiles on server which are not managed. Names: '${serverQpsUnmanaged.map((e) => e.name)}'`,\n    );\n  }\n\n  for (const unmanagedServerQp of serverQpsUnmanaged) {\n    // CFs matching. Hint: make sure to execute the method with updated CFs. Otherwise if we create CFs and update existing profiles those could be missing.\n    const serverProfileCFMap = new Map(unmanagedServerQp.formatItems!.map((obj) => [obj.name!, obj]));\n    const scoringForQP = scoring.get(unmanagedServerQp.name!);\n    let scoringDiff = false;\n    const changeList: string[] = [];\n\n    if (scoringForQP != null) {\n      const newCFFormats: MergedProfileFormatItemResource[] = [];\n\n      for (const [scoreKey, scoreValue] of scoringForQP.entries()) {\n        const serverCF = serverProfileCFMap.get(scoreKey);\n        serverProfileCFMap.delete(scoreKey);\n\n        // TODO (1): check where best handled\n        if (scoreValue.score == null) {\n          newCFFormats.push({ ...serverCF });\n        } else {\n          if (serverCF?.score !== scoreValue.score) {\n            scoringDiff = true;\n            changeList.push(`CF diff '${scoreValue.name}': server: '${serverCF?.score}' - expected: '${scoreValue.score}'`);\n            newCFFormats.push({ ...serverCF, score: scoreValue.score });\n          } else {\n            newCFFormats.push({ ...serverCF });\n          }\n        }\n      }\n\n      const missingCfs = Array.from(serverProfileCFMap.values());\n\n      newCFFormats.push(...missingCfs);\n\n      unmanagedServerQp.formatItems = newCFFormats;\n    } else {\n      logger.debug(`No custom format scoring for unmanaged QualityProfile '${unmanagedServerQp.name!}' found`);\n    }\n\n    logger.debug(`Unmanaged QualityProfile (${unmanagedServerQp.name}) - In Sync: ${changeList.length <= 0}, CF Changes: ${scoringDiff}}`);\n\n    if (scoringDiff) {\n      changedQPs.push(unmanagedServerQp);\n    } else {\n      noChangedQPs.push(unmanagedServerQp.name!);\n    }\n\n    if (changeList.length > 0) {\n      logger.debug(changeList, `ChangeList for unmanaged QualityProfile '${unmanagedServerQp.name}'`);\n    }\n  }\n\n  return { create: createQPs, changedQPs: changedQPs, noChanges: noChangedQPs };\n};\n\nexport const filterInvalidQualityProfiles = (profiles: ConfigQualityProfile[]): ConfigQualityProfile[] => {\n  return profiles.filter((p) => {\n    if (p.name == null) {\n      logger.warn(p, `QualityProfile filtered because no name provided`);\n      return false;\n    }\n    if (p.qualities == null) {\n      logger.warn(`QualityProfile: '${p.name}' filtered because no qualities provided`);\n      return false;\n    }\n    if (p.upgrade == null) {\n      logger.warn(`QualityProfile: '${p.name}' filtered because no upgrade definition provided`);\n      return false;\n    }\n\n    return true;\n  });\n};\n\nexport const getUnmanagedQualityProfiles = (\n  serverQP: MergedQualityProfileResource[],\n  configQp: ConfigQualityProfile[],\n): MergedQualityProfileResource[] => {\n  const managedProfileNames = new Set(configQp.map((profile) => profile.name));\n\n  return serverQP.filter((profile) => profile.name && !managedProfileNames.has(profile.name));\n};\n\n/**\n * Detects and warns about mutually exclusive custom formats in quality profiles.\n *\n * @param cfMap - The CF mapping containing carrIdMapping for name resolution\n * @param config - Merged config instance with custom_formats and quality_profiles\n * @param conflicts - List of TRaSH conflict groups to check\n */\nexport const checkForConflictingCFs = (\n  cfMap: CFProcessing,\n  config: MergedConfigInstance,\n  conflicts: TrashCFConflict[] | undefined,\n): void => {\n  if (!conflicts || conflicts.length === 0) {\n    return;\n  }\n\n  // Build quality profile -> Set<trash_id> mapping from merged config\n  const profileToTrashIds = new Map<string, Set<string>>();\n\n  for (const { trash_ids, assign_scores_to } of config.custom_formats) {\n    if (!trash_ids || !assign_scores_to) {\n      continue;\n    }\n\n    for (const profile of assign_scores_to) {\n      let profileSet = profileToTrashIds.get(profile.name);\n\n      if (!profileSet) {\n        profileSet = new Set();\n        profileToTrashIds.set(profile.name, profileSet);\n      }\n\n      for (const trashId of trash_ids) {\n        profileSet.add(trashId);\n      }\n    }\n  }\n\n  // For each conflict group, find profiles containing 2+ conflicting CFs\n  for (const conflict of conflicts) {\n    const { trash_id: conflictId, name: conflictName, custom_formats: conflictingCFs } = conflict;\n\n    for (const [profileName, profileTrashIds] of profileToTrashIds.entries()) {\n      // Find how many CFs from this conflict group are in the profile\n      const matchedCFs = conflictingCFs.filter((cf) => profileTrashIds.has(cf.trash_id));\n\n      if (matchedCFs.length >= 2) {\n        // Resolve display names: TRaSH conflicts first, then carrIdMapping, then raw id\n        const cfNames = matchedCFs\n          .map((cf) => {\n            const carrConfig = cfMap.carrIdMapping.get(cf.trash_id);\n            return carrConfig?.carrConfig.name || cf.name || cf.trash_id;\n          })\n          .join(\", \");\n\n        const cfIds = matchedCFs.map((cf) => cf.trash_id).join(\", \");\n\n        logger.warn(\n          `QualityProfile '${profileName}': Conflicting CustomFormats detected [${cfNames}] (ids: [${cfIds}]). ` +\n            `TRaSH marks these as mutually exclusive in conflict group '${conflictName}' (id: ${conflictId}). ` +\n            `Sync continues unchanged.`,\n        );\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "src/recyclarr-importer.ts",
    "content": "import { default as fs } from \"node:fs\";\nimport yaml from \"yaml\";\nimport { getConfig } from \"./config\";\nimport { logger } from \"./logger\";\nimport { MappedTemplates } from \"./types/common.types\";\nimport { RecyclarrArrSupported, RecyclarrTemplates } from \"./types/recyclarr.types\";\nimport { cloneGitRepo, recyclarrRepoPaths } from \"./util\";\n\nconst DEFAULT_RECYCLARR_GIT_URL = \"https://github.com/recyclarr/config-templates\";\n\nexport const cloneRecyclarrTemplateRepo = async () => {\n  logger.info(`Checking Recyclarr repo ...`);\n\n  const rootPath = recyclarrRepoPaths.root;\n  const applicationConfig = getConfig();\n  const gitUrl = applicationConfig.recyclarrConfigUrl ?? DEFAULT_RECYCLARR_GIT_URL;\n  const revision = applicationConfig.recyclarrRevision ?? \"master\";\n  const sparseDisabled = applicationConfig.enableFullGitClone === true;\n\n  const cloneResult = await cloneGitRepo(rootPath, gitUrl, revision, {\n    disabled: sparseDisabled,\n    sparseDirs: [\"radarr/\", \"sonarr/\"],\n  });\n  logger.info(`Recyclarr repo: ref[${cloneResult.ref}], hash[${cloneResult.hash}], path[${cloneResult.localPath}]`);\n};\n\nexport const loadRecyclarrTemplates = (arrType: RecyclarrArrSupported): Map<string, MappedTemplates> => {\n  const map = new Map<string, RecyclarrTemplates>();\n\n  const fillMap = (path: string) => {\n    const files = fs\n      .readdirSync(`${path}`, { recursive: true, encoding: \"utf8\" })\n      .filter((fn) => fn.endsWith(\"yaml\") || fn.endsWith(\"yml\"));\n\n    // recyclarr config templates can have sub folders\n    files.forEach((f) => {\n      const filename = f.substring(f.lastIndexOf(\"/\") + 1, f.lastIndexOf(\".\"));\n      map.set(filename, yaml.parse(fs.readFileSync(`${path}/${f}`, \"utf8\")));\n    });\n  };\n\n  if (arrType === \"RADARR\") {\n    fillMap(recyclarrRepoPaths.radarrCF);\n    fillMap(recyclarrRepoPaths.radarrQD);\n    fillMap(recyclarrRepoPaths.radarrQP);\n  } else {\n    fillMap(recyclarrRepoPaths.sonarrCF);\n    fillMap(recyclarrRepoPaths.sonarrQD);\n    fillMap(recyclarrRepoPaths.sonarrQP);\n  }\n\n  logger.debug(`Found ${map.size} Recyclarr templates.`);\n\n  return new Map(\n    Array.from(map, ([k, v]) => {\n      const customFormats = v.custom_formats?.map((cf) => {\n        // Changes from Recyclarr 7.2.0: https://github.com/recyclarr/recyclarr/releases/tag/v7.2.0\n        if (cf.assign_scores_to == null && cf.quality_profiles == null) {\n          logger.warn(`Recyclarr Template \"${k}\" does not provide correct profile for custom format. Ignoring.`);\n        }\n\n        if (cf.quality_profiles) {\n          logger.warn(\n            `Deprecated: (Recyclarr Template '${k}') For custom_formats please rename 'quality_profiles' to 'assign_scores_to'. See recyclarr v7.2.0`,\n          );\n        }\n        return { ...cf, assign_scores_to: cf.assign_scores_to ?? cf.quality_profiles ?? [] };\n      });\n\n      return [\n        k,\n        {\n          ...v,\n          custom_formats: customFormats,\n        },\n      ];\n    }),\n  );\n};\n"
  },
  {
    "path": "src/remotePaths/remotePath.types.test.ts",
    "content": "import { describe, it, expect } from \"vitest\";\nimport { RemotePathConfigSchema } from \"./remotePath.types\";\n\ndescribe(\"RemotePathTypes\", () => {\n  it(\"should validate correct remote path config\", () => {\n    const config = {\n      host: \"transmission\",\n      remote_path: \"/downloads\",\n      local_path: \"/data/downloads\",\n    };\n    expect(() => RemotePathConfigSchema.parse(config)).not.toThrow();\n  });\n\n  it(\"should reject missing host\", () => {\n    const config = {\n      remote_path: \"/downloads\",\n      local_path: \"/data/downloads\",\n    };\n    expect(() => RemotePathConfigSchema.parse(config)).toThrow();\n  });\n\n  it(\"should reject empty host\", () => {\n    const config = {\n      host: \"\",\n      remote_path: \"/downloads\",\n      local_path: \"/data/downloads\",\n    };\n    expect(() => RemotePathConfigSchema.parse(config)).toThrow();\n  });\n\n  it(\"should reject missing remote_path\", () => {\n    const config = {\n      host: \"transmission\",\n      local_path: \"/data/downloads\",\n    };\n    expect(() => RemotePathConfigSchema.parse(config)).toThrow();\n  });\n\n  it(\"should reject missing local_path\", () => {\n    const config = {\n      host: \"transmission\",\n      remote_path: \"/downloads\",\n    };\n    expect(() => RemotePathConfigSchema.parse(config)).toThrow();\n  });\n});\n"
  },
  {
    "path": "src/remotePaths/remotePath.types.ts",
    "content": "import { z } from \"zod\";\nimport { ArrType } from \"../types/common.types\";\nimport { InputConfigRemotePath } from \"../types/config.types\";\n\n/**\n * Zod schema for validating remote path configuration\n */\nexport const RemotePathConfigSchema = z.object({\n  host: z.string().min(1, \"Host is required\"),\n  remote_path: z\n    .string()\n    .min(1, \"Remote path is required\")\n    .regex(/^([a-zA-Z]:|\\/)/, \"Remote path must be an absolute path (start with / or X:/ for Windows)\"),\n  local_path: z\n    .string()\n    .min(1, \"Local path is required\")\n    .regex(/^([a-zA-Z]:|\\/)/, \"Local path must be an absolute path (start with / or X:/ for Windows)\"),\n});\n\n/**\n * Result of a remote path mapping sync operation\n */\nexport interface RemotePathSyncResult {\n  created: number;\n  updated: number;\n  deleted: number;\n  unchanged: number;\n  arrType: ArrType;\n}\n\n/**\n * Remote path mapping resource (common interface used by syncer)\n * Individual clients use their specific generated types\n */\nexport interface RemotePathMappingResource {\n  id?: number;\n  host?: string | null;\n  remotePath?: string | null;\n  localPath?: string | null;\n}\n\n/**\n * Internal diff calculation result\n */\nexport interface RemotePathDiff {\n  toCreate: InputConfigRemotePath[];\n  toUpdate: Array<{ id: number; config: InputConfigRemotePath }>;\n  toDelete: Array<{ id: number }>;\n  unchanged: number;\n}\n"
  },
  {
    "path": "src/remotePaths/remotePathSyncer.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { syncRemotePaths } from \"./remotePathSyncer\";\nimport { getUnifiedClient, getSpecificClient } from \"../clients/unified-client\";\nimport { RemotePathMappingResource } from \"./remotePath.types\";\n\n// Mock env - use importOriginal to preserve other env functions\nvi.mock(\"../env\", async (importOriginal) => {\n  const actual = await importOriginal<typeof import(\"../env\")>();\n  return {\n    ...actual,\n    getEnvs: vi.fn(() => ({\n      DRY_RUN: false,\n      LOG_LEVEL: \"silent\",\n      DEBUG_CREATE_FILES: false,\n      CONFIGARR_VERSION: \"test\",\n      ROOT_PATH: \"/tmp/test\",\n    })),\n  };\n});\n\nvi.mock(\"../clients/unified-client\");\nvi.mock(\"../logger\");\n\ndescribe(\"remotePathSyncer\", () => {\n  beforeEach(() => {\n    vi.clearAllMocks();\n  });\n\n  it(\"should return early when no remote_paths in config\", async () => {\n    const config: any = {\n      custom_formats: [],\n      quality_profiles: [],\n      download_clients: {},\n    };\n\n    const result = await syncRemotePaths(\"RADARR\", config);\n\n    expect(result).toEqual({\n      created: 0,\n      updated: 0,\n      deleted: 0,\n      unchanged: 0,\n      arrType: \"RADARR\",\n    });\n  });\n\n  // Note: Config validation (empty strings, duplicates) now happens earlier in validateConfig (config.ts)\n  // These tests are removed as validation is no longer part of the syncer's responsibility\n\n  it(\"should return zero counts when no changes needed\", async () => {\n    const config: any = {\n      custom_formats: [],\n      quality_profiles: [],\n      download_clients: {\n        remote_paths: [],\n      },\n    };\n\n    const result = await syncRemotePaths(\"RADARR\", config);\n\n    expect(result.created).toBe(0);\n    expect(result.updated).toBe(0);\n    expect(result.deleted).toBe(0);\n    expect(result.arrType).toBe(\"RADARR\");\n  });\n\n  describe(\"integration tests\", () => {\n    let mockRadarrClient: any;\n\n    beforeEach(() => {\n      mockRadarrClient = {\n        getRemotePathMappings: vi.fn(),\n        createRemotePathMapping: vi.fn(),\n        updateRemotePathMapping: vi.fn(),\n        deleteRemotePathMapping: vi.fn(),\n      };\n\n      vi.mocked(getUnifiedClient).mockReturnValue({\n        api: mockRadarrClient,\n      } as any);\n\n      // Mock getSpecificClient for all arrTypes\n      vi.mocked(getSpecificClient).mockImplementation((arrType?: string) => {\n        return mockRadarrClient;\n      });\n    });\n\n    it(\"should handle already exists error by falling back to update\", async () => {\n      const serverMappings: RemotePathMappingResource[] = [\n        { id: 1, host: \"transmission\", remotePath: \"/downloads\", localPath: \"/old/path\" },\n      ];\n\n      mockRadarrClient.getRemotePathMappings.mockResolvedValue(serverMappings);\n      mockRadarrClient.createRemotePathMapping.mockRejectedValue(new Error(\"RemotePath already configured\"));\n      mockRadarrClient.updateRemotePathMapping.mockResolvedValue({\n        id: 1,\n        host: \"transmission\",\n        remotePath: \"/downloads\",\n        localPath: \"/new/path\",\n      });\n\n      const config: any = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          remote_paths: [\n            {\n              host: \"transmission\",\n              remote_path: \"/downloads\",\n              local_path: \"/new/path\",\n            },\n          ],\n        },\n      };\n\n      const result = await syncRemotePaths(\"RADARR\", config);\n\n      expect(result.updated).toBe(1);\n      expect(mockRadarrClient.updateRemotePathMapping).toHaveBeenCalledWith(\n        \"1\",\n        expect.objectContaining({\n          id: 1,\n          host: \"transmission\",\n          remotePath: \"/downloads\",\n          localPath: \"/new/path\",\n        }),\n      );\n    });\n\n    it(\"should normalize paths with trailing slashes for comparison\", async () => {\n      const serverMappings: RemotePathMappingResource[] = [\n        { id: 1, host: \"transmission\", remotePath: \"/downloads/\", localPath: \"/data/downloads\" },\n      ];\n\n      mockRadarrClient.getRemotePathMappings.mockResolvedValue(serverMappings);\n\n      const config: any = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          remote_paths: [\n            {\n              host: \"transmission\",\n              remote_path: \"/downloads\",\n              local_path: \"/data/downloads\",\n            },\n          ],\n        },\n      };\n\n      const result = await syncRemotePaths(\"RADARR\", config);\n\n      // Should be detected as unchanged (paths are the same after normalization)\n      expect(result.unchanged).toBe(1);\n      expect(result.created).toBe(0);\n      expect(result.updated).toBe(0);\n      expect(mockRadarrClient.createRemotePathMapping).not.toHaveBeenCalled();\n    });\n\n    it(\"should delete all mappings when delete_unmanaged_remote_paths is true with empty array\", async () => {\n      const serverMappings: RemotePathMappingResource[] = [\n        { id: 1, host: \"transmission\", remotePath: \"/downloads\", localPath: \"/data/downloads\" },\n        { id: 2, host: \"qbittorrent\", remotePath: \"/downloads\", localPath: \"/data/downloads2\" },\n      ];\n\n      mockRadarrClient.getRemotePathMappings.mockResolvedValue(serverMappings);\n      mockRadarrClient.deleteRemotePathMapping.mockResolvedValue(undefined);\n\n      const config: any = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          remote_paths: [],\n          delete_unmanaged_remote_paths: true,\n        },\n      };\n\n      const result = await syncRemotePaths(\"RADARR\", config);\n\n      expect(result.deleted).toBe(2);\n      expect(mockRadarrClient.deleteRemotePathMapping).toHaveBeenCalledWith(\"1\");\n      expect(mockRadarrClient.deleteRemotePathMapping).toHaveBeenCalledWith(\"2\");\n    });\n\n    it(\"should create and update mappings based on diff\", async () => {\n      const serverMappings: RemotePathMappingResource[] = [\n        { id: 1, host: \"transmission\", remotePath: \"/downloads\", localPath: \"/old/path\" },\n        { id: 2, host: \"qbittorrent\", remotePath: \"/downloads\", localPath: \"/data/downloads\" },\n      ];\n\n      mockRadarrClient.getRemotePathMappings.mockResolvedValue(serverMappings);\n      mockRadarrClient.createRemotePathMapping.mockResolvedValue({\n        id: 3,\n        host: \"deluge\",\n        remotePath: \"/downloads\",\n        localPath: \"/data/downloads\",\n      });\n      mockRadarrClient.updateRemotePathMapping.mockResolvedValue({\n        id: 1,\n        host: \"transmission\",\n        remotePath: \"/downloads\",\n        localPath: \"/new/path\",\n      });\n\n      const config: any = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          remote_paths: [\n            {\n              host: \"transmission\",\n              remote_path: \"/downloads\",\n              local_path: \"/new/path\", // Update\n            },\n            {\n              host: \"deluge\",\n              remote_path: \"/downloads\",\n              local_path: \"/data/downloads\", // Create\n            },\n          ],\n        },\n      };\n\n      const result = await syncRemotePaths(\"RADARR\", config);\n\n      expect(result.created).toBe(1);\n      expect(result.updated).toBe(1);\n      expect(result.deleted).toBe(1); // qbittorrent deleted\n      expect(mockRadarrClient.createRemotePathMapping).toHaveBeenCalledWith({\n        host: \"deluge\",\n        remotePath: \"/downloads\",\n        localPath: \"/data/downloads\",\n      });\n      expect(mockRadarrClient.updateRemotePathMapping).toHaveBeenCalledWith(\n        \"1\",\n        expect.objectContaining({\n          id: 1,\n          host: \"transmission\",\n          remotePath: \"/downloads\",\n          localPath: \"/new/path\",\n        }),\n      );\n    });\n\n    it(\"should handle different hosts with same remote path\", async () => {\n      const serverMappings: RemotePathMappingResource[] = [];\n\n      mockRadarrClient.getRemotePathMappings.mockResolvedValue(serverMappings);\n      mockRadarrClient.createRemotePathMapping\n        .mockResolvedValueOnce({\n          id: 1,\n          host: \"transmission\",\n          remotePath: \"/downloads\",\n          localPath: \"/data/downloads\",\n        })\n        .mockResolvedValueOnce({\n          id: 2,\n          host: \"qbittorrent\",\n          remotePath: \"/downloads\",\n          localPath: \"/data/downloads2\",\n        });\n\n      const config: any = {\n        custom_formats: [],\n        quality_profiles: [],\n        download_clients: {\n          remote_paths: [\n            {\n              host: \"transmission\",\n              remote_path: \"/downloads\",\n              local_path: \"/data/downloads\",\n            },\n            {\n              host: \"qbittorrent\",\n              remote_path: \"/downloads\",\n              local_path: \"/data/downloads2\",\n            },\n          ],\n        },\n      };\n\n      const result = await syncRemotePaths(\"RADARR\", config);\n\n      expect(result.created).toBe(2);\n      expect(mockRadarrClient.createRemotePathMapping).toHaveBeenCalledTimes(2);\n    });\n  });\n});\n"
  },
  {
    "path": "src/remotePaths/remotePathSyncer.ts",
    "content": "import { getSpecificClient } from \"../clients/unified-client\";\nimport { logger } from \"../logger\";\nimport { ArrType } from \"../types/common.types\";\nimport { RemotePathMappingResource, RemotePathSyncResult, RemotePathDiff } from \"./remotePath.types\";\nimport { InputConfigRemotePath, MergedConfigInstance } from \"../types/config.types\";\nimport { getEnvs } from \"../env\";\n\n/**\n * Normalize a path by removing trailing slashes\n * Radarr may add trailing slashes but we need consistent comparison\n */\nfunction normalizePath(path: string): string {\n  return path.replace(/\\/+$/, \"\");\n}\n\n/**\n * Create a composite key for matching remote path mappings\n * Uses host + remote_path combination for uniqueness\n */\nfunction createCompositeKey(host: string, remotePath: string): string {\n  return `${host}||${normalizePath(remotePath)}`;\n}\n\n/**\n * Calculate the diff between config and server remote path mappings\n */\nfunction calculateDiff(configs: InputConfigRemotePath[], serverMappings: RemotePathMappingResource[]): RemotePathDiff {\n  const configMap = new Map<string, InputConfigRemotePath>();\n  const serverMap = new Map<string, RemotePathMappingResource>();\n\n  // Build config map\n  for (const config of configs) {\n    const key = createCompositeKey(config.host, config.remote_path);\n    configMap.set(key, config);\n  }\n\n  // Build server map\n  for (const mapping of serverMappings) {\n    if (mapping.host && mapping.remotePath) {\n      const key = createCompositeKey(mapping.host, mapping.remotePath);\n      serverMap.set(key, mapping);\n    }\n  }\n\n  const toCreate: InputConfigRemotePath[] = [];\n  const toUpdate: Array<{ id: number; config: InputConfigRemotePath }> = [];\n  let unchanged = 0;\n\n  // Find items to create or update\n  for (const [key, config] of configMap) {\n    const serverMapping = serverMap.get(key);\n    if (!serverMapping) {\n      toCreate.push(config);\n    } else if (serverMapping.localPath !== config.local_path) {\n      if (serverMapping.id) {\n        toUpdate.push({ id: serverMapping.id, config });\n      }\n    } else {\n      unchanged++;\n    }\n  }\n\n  // Find items to delete\n  const toDelete = Array.from(serverMap.entries())\n    .filter(([key]) => !configMap.has(key))\n    .filter(([, mapping]) => !!mapping.id)\n    .map(([, mapping]) => ({ id: mapping.id! }));\n\n  return { toCreate, toUpdate, toDelete, unchanged };\n}\n\n/**\n * Sync remote path mappings for a specific *Arr instance\n */\nexport async function syncRemotePaths(arrType: ArrType, config: MergedConfigInstance): Promise<RemotePathSyncResult> {\n  const remotePaths = config.download_clients?.remote_paths;\n  const deleteUnmanaged = config.download_clients?.delete_unmanaged_remote_paths ?? false;\n\n  // If remote_paths is undefined/not present, skip management entirely\n  if (remotePaths === undefined) {\n    logger.debug(`No remote path mappings specified for ${arrType}`);\n    return { created: 0, updated: 0, deleted: 0, unchanged: 0, arrType };\n  }\n\n  // If remote_paths is empty array [], skip unless delete_unmanaged is enabled\n  // This allows users to opt-in to deleting all remote paths with delete_unmanaged_remote_paths: true\n  if (remotePaths.length === 0) {\n    if (!deleteUnmanaged) {\n      logger.debug(`No remote path mappings specified for ${arrType}`);\n      return { created: 0, updated: 0, deleted: 0, unchanged: 0, arrType };\n    }\n    logger.debug(`Empty remote_paths with delete_unmanaged_remote_paths enabled for ${arrType} - will delete all existing mappings`);\n  }\n\n  try {\n    // Config validation happens earlier in validateConfig (config.ts)\n    // Get specific client for this arrType - TypeScript infers the correct type\n    const client = getSpecificClient(arrType);\n\n    // Fetch current server mappings\n    logger.debug(`Fetching remote path mappings from ${arrType}...`);\n    const serverMappings = await client.getRemotePathMappings();\n\n    // Calculate diff\n    const diff = calculateDiff(remotePaths, serverMappings);\n\n    logger.debug(\n      `Remote path mapping diff for ${arrType}: create=${diff.toCreate.length}, update=${diff.toUpdate.length}, delete=${diff.toDelete.length}, unchanged=${diff.unchanged}`,\n    );\n\n    // Check if any changes needed\n    if (diff.toCreate.length === 0 && diff.toUpdate.length === 0 && diff.toDelete.length === 0) {\n      logger.info(`Remote path mappings for ${arrType} are already up-to-date`);\n      return { created: 0, updated: 0, deleted: 0, unchanged: diff.unchanged, arrType };\n    }\n\n    logger.info(`Remote path mapping changes detected for ${arrType}`);\n\n    // Respect dry-run mode\n    if (getEnvs().DRY_RUN) {\n      logger.info(\n        `DryRun: Would create ${diff.toCreate.length}, update ${diff.toUpdate.length}, delete ${diff.toDelete.length} remote path mappings for ${arrType}`,\n      );\n      return {\n        created: diff.toCreate.length,\n        updated: diff.toUpdate.length,\n        deleted: diff.toDelete.length,\n        unchanged: diff.unchanged,\n        arrType,\n      };\n    }\n\n    // Execute operations\n    let created = 0;\n    let updated = 0;\n    let deleted = 0;\n\n    // Create new mappings\n    for (const config of diff.toCreate) {\n      try {\n        await client.createRemotePathMapping({\n          host: config.host,\n          remotePath: config.remote_path,\n          localPath: config.local_path,\n        });\n        created++;\n        logger.info(`Created remote path mapping: ${config.host} => ${config.remote_path} -> ${config.local_path}`);\n      } catch (error) {\n        // Check if the error is because the remotePath already exists\n        const errorMsg = error instanceof Error ? error.message : String(error);\n        if (errorMsg.includes(\"RemotePath already configured\") || errorMsg.includes(\"already exists\")) {\n          // Try to find the existing mapping and update it (match by host AND remotePath)\n          const normalizedConfigPath = normalizePath(config.remote_path);\n          const existingMapping = serverMappings.find(\n            (m: RemotePathMappingResource) =>\n              m.host === config.host && m.remotePath && normalizePath(m.remotePath) === normalizedConfigPath,\n          );\n          if (existingMapping && existingMapping.id) {\n            logger.debug(`RemotePath '${config.host} + ${config.remote_path}' already exists. Attempting to update instead.`);\n            try {\n              await client.updateRemotePathMapping(existingMapping.id.toString(), {\n                id: existingMapping.id,\n                host: config.host,\n                remotePath: config.remote_path,\n                localPath: config.local_path,\n              });\n              updated++;\n              logger.info(`Updated existing remote path mapping: ${config.host} => ${config.remote_path} -> ${config.local_path}`);\n              continue;\n            } catch (updateError) {\n              logger.error(\n                `Failed to update remote path mapping: ${updateError instanceof Error ? updateError.message : String(updateError)}`,\n              );\n              throw updateError;\n            }\n          }\n        }\n        logger.error(`Failed to create remote path mapping for ${config.host}/${config.remote_path}: ${errorMsg}`);\n        throw error;\n      }\n    }\n\n    // Update existing mappings\n    for (const { id, config } of diff.toUpdate) {\n      try {\n        await client.updateRemotePathMapping(id.toString(), {\n          id,\n          host: config.host,\n          remotePath: config.remote_path,\n          localPath: config.local_path,\n        });\n        updated++;\n        logger.info(`Updated remote path mapping: ${config.host} => ${config.remote_path} -> ${config.local_path}`);\n      } catch (error) {\n        logger.error(`Failed to update remote path mapping ${id}: ${error instanceof Error ? error.message : String(error)}`);\n        throw error;\n      }\n    }\n\n    // Delete removed mappings\n    for (const { id } of diff.toDelete) {\n      try {\n        await client.deleteRemotePathMapping(id.toString());\n        deleted++;\n        logger.info(`Deleted remote path mapping: ${id}`);\n      } catch (error) {\n        logger.error(`Failed to delete remote path mapping ${id}: ${error instanceof Error ? error.message : String(error)}`);\n        throw error;\n      }\n    }\n\n    logger.info(`Successfully synced remote path mappings for ${arrType}: created=${created}, updated=${updated}, deleted=${deleted}`);\n    return { created, updated, deleted, unchanged: diff.unchanged, arrType };\n  } catch (error: unknown) {\n    const errorMessage = error instanceof Error ? error.message : String(error);\n    logger.error(`Failed to sync remote path mappings for ${arrType}: ${errorMessage}`);\n    throw new Error(`Remote path mapping sync failed for ${arrType}: ${errorMessage}`);\n  }\n}\n"
  },
  {
    "path": "src/rootFolder/rootFolder.types.ts",
    "content": "import { InputConfigRootFolder } from \"../types/config.types\";\nimport { MergedRootFolderResource } from \"../types/merged.types\";\n\n// Shared types for root folder operations\nexport interface RootFolderDiff<TConfig extends InputConfigRootFolder = InputConfigRootFolder> {\n  missingOnServer: TConfig[];\n  notAvailableAnymore: MergedRootFolderResource[];\n  changed: Array<{ config: TConfig; server: MergedRootFolderResource }>;\n}\n\nexport interface RootFolderSyncResult {\n  added: number;\n  removed: number;\n  updated: number;\n}\n"
  },
  {
    "path": "src/rootFolder/rootFolderBase.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { GenericRootFolderSync } from \"./rootFolderBase\";\nimport { getUnifiedClient } from \"../clients/unified-client\";\nimport { ServerCache } from \"../cache\";\n\n// Mock the unified client\nvi.mock(\"../clients/unified-client\", () => ({\n  getUnifiedClient: vi.fn(),\n}));\n\ndescribe(\"GenericRootFolderSync\", () => {\n  const mockApi = {\n    getRootfolders: vi.fn(),\n  };\n\n  let serverCache: ServerCache;\n\n  beforeEach(() => {\n    vi.clearAllMocks();\n    (getUnifiedClient as any).mockReturnValue(mockApi);\n    serverCache = new ServerCache([], [], [], []);\n  });\n\n  describe(\"calculateDiff\", () => {\n    it(\"should handle string root folders\", async () => {\n      mockApi.getRootfolders.mockResolvedValue([\"/existing\"]);\n\n      const sync = new GenericRootFolderSync(\"RADARR\");\n      const result = await sync.calculateDiff([\"/existing\", \"/new\"], serverCache);\n\n      expect(result).toEqual({\n        missingOnServer: [\"/new\"],\n        notAvailableAnymore: [],\n        changed: [],\n      });\n    });\n\n    it(\"should detect root folders not available anymore\", async () => {\n      mockApi.getRootfolders.mockResolvedValue([\"/old-folder\"]);\n\n      const sync = new GenericRootFolderSync(\"RADARR\");\n      const result = await sync.calculateDiff([\"/new-folder\"], serverCache);\n\n      expect(result).toEqual({\n        missingOnServer: [\"/new-folder\"],\n        notAvailableAnymore: [\"/old-folder\"],\n        changed: [],\n      });\n    });\n\n    it(\"should handle mixed string and object root folders\", async () => {\n      mockApi.getRootfolders.mockResolvedValue([\"/string-folder\", \"/object-folder\"]);\n\n      const sync = new GenericRootFolderSync(\"RADARR\");\n      const result = await sync.calculateDiff(\n        [\n          \"/string-folder\",\n          { path: \"/object-folder\", name: \"object\", metadata_profile: \"Standard\", quality_profile: \"Any\" },\n          { path: \"/new-object\", name: \"new\", metadata_profile: \"Standard\", quality_profile: \"Any\" },\n        ],\n        serverCache,\n      );\n\n      expect(result).toEqual({\n        missingOnServer: [{ path: \"/new-object\", name: \"new\", metadata_profile: \"Standard\", quality_profile: \"Any\" }],\n        notAvailableAnymore: [],\n        changed: [],\n      });\n    });\n\n    it(\"should handle empty config\", async () => {\n      mockApi.getRootfolders.mockResolvedValue([\"/server-folder\"]);\n\n      const sync = new GenericRootFolderSync(\"RADARR\");\n      const result = await sync.calculateDiff([], serverCache);\n\n      expect(result).toEqual({\n        missingOnServer: [],\n        notAvailableAnymore: [\"/server-folder\"],\n        changed: [],\n      });\n    });\n\n    it(\"should handle null/undefined config\", async () => {\n      mockApi.getRootfolders.mockResolvedValue([]);\n\n      const sync = new GenericRootFolderSync(\"RADARR\");\n      const result = await sync.calculateDiff(null as any, serverCache);\n\n      expect(result).toBeNull();\n    });\n  });\n\n  describe(\"resolveRootFolderConfig\", () => {\n    it(\"should handle string config for non-Lidarr\", async () => {\n      const sync = new GenericRootFolderSync(\"RADARR\");\n      const result = await sync.resolveRootFolderConfig(\"/path/to/folder\", serverCache);\n      expect(result).toEqual({ path: \"/path/to/folder\" });\n    });\n  });\n});\n"
  },
  {
    "path": "src/rootFolder/rootFolderBase.ts",
    "content": "import { MergedRootFolderResource } from \"../types/merged.types\";\nimport { ServerCache } from \"../cache\";\nimport { getUnifiedClient, IArrClient } from \"../clients/unified-client\";\nimport { getEnvs } from \"../env\";\nimport { logger } from \"../logger\";\nimport { ArrType } from \"../types/common.types\";\nimport { InputConfigRootFolder } from \"../types/config.types\";\nimport { RootFolderDiff, RootFolderSyncResult } from \"./rootFolder.types\";\n\n// Base class for root folder synchronization\nexport abstract class BaseRootFolderSync<TConfig extends InputConfigRootFolder = InputConfigRootFolder> {\n  protected api!: IArrClient;\n  protected logger = logger;\n\n  abstract calculateDiff(rootFolders: TConfig[], serverCache: ServerCache): Promise<RootFolderDiff<TConfig> | null>;\n  public abstract resolveRootFolderConfig(config: TConfig, serverCache: ServerCache): Promise<MergedRootFolderResource>;\n\n  async syncRootFolders(rootFolders: TConfig[], serverCache: ServerCache): Promise<RootFolderSyncResult> {\n    const diff = await this.calculateDiff(rootFolders, serverCache);\n\n    if (!diff) {\n      return { added: 0, removed: 0, updated: 0 };\n    }\n\n    if (getEnvs().DRY_RUN) {\n      this.logger.info(\"DryRun: Would update RootFolders.\");\n      return { added: diff.missingOnServer.length, removed: diff.notAvailableAnymore.length, updated: diff.changed.length };\n    }\n\n    let added = 0,\n      removed = 0,\n      updated = 0;\n\n    // Remove folders not in config\n    for (const folder of diff.notAvailableAnymore) {\n      this.logger.info(`Deleting RootFolder not available anymore: ${folder.path}`);\n      await this.api.deleteRootFolder(`${folder.id}`);\n      removed++;\n    }\n\n    // Add missing folders\n    for (const folder of diff.missingOnServer) {\n      this.logger.info(`Adding RootFolder missing on server: ${typeof folder === \"string\" ? folder : folder.path}`);\n      const resolvedConfig = await this.resolveRootFolderConfig(folder, serverCache);\n      await this.api.addRootFolder(resolvedConfig);\n      added++;\n    }\n\n    // Update changed folders\n    for (const { config, server } of diff.changed) {\n      this.logger.info(`Updating RootFolder: ${typeof config === \"string\" ? config : config.path}`);\n      const resolvedConfig = await this.resolveRootFolderConfig(config, serverCache);\n      await this.api.updateRootFolder(`${server.id}`, resolvedConfig);\n      updated++;\n    }\n\n    if (added > 0 || removed > 0 || updated > 0) {\n      this.logger.info(`Updated RootFolders: +${added} -${removed} ~${updated}`);\n    }\n\n    return { added, removed, updated };\n  }\n\n  protected async loadRootFoldersFromServer(): Promise<MergedRootFolderResource[]> {\n    const result = await this.api.getRootfolders();\n    return result as MergedRootFolderResource[];\n  }\n\n  protected abstract getArrType(): ArrType;\n}\n\n// Generic sync for most arr types (Radarr, Sonarr, etc.)\nexport class GenericRootFolderSync extends BaseRootFolderSync<InputConfigRootFolder> {\n  protected api: IArrClient = getUnifiedClient();\n\n  constructor(private arrType: ArrType) {\n    super();\n  }\n\n  protected getArrType(): ArrType {\n    return this.arrType;\n  }\n\n  public async resolveRootFolderConfig(config: InputConfigRootFolder, serverCache: ServerCache): Promise<MergedRootFolderResource> {\n    if (typeof config === \"string\") {\n      return { path: config };\n    }\n\n    // For non-Lidarr types, just return the path\n    return { path: config.path };\n  }\n\n  async calculateDiff(\n    rootFolders: InputConfigRootFolder[],\n    serverCache: ServerCache,\n  ): Promise<RootFolderDiff<InputConfigRootFolder> | null> {\n    if (rootFolders == null) {\n      this.logger.debug(`Config 'root_folders' not specified. Ignoring.`);\n      return null;\n    }\n\n    const serverData = await this.loadRootFoldersFromServer();\n\n    // If config is empty array, all server folders should be removed\n    if (rootFolders.length === 0) {\n      const notAvailableAnymore = serverData.map((folder) => (typeof folder === \"string\" ? folder : folder));\n      this.logger.info(`Found ${notAvailableAnymore.length} differences for root folders.`);\n\n      return {\n        missingOnServer: [],\n        notAvailableAnymore,\n        changed: [],\n      };\n    }\n\n    // For generic arr types, only compare paths\n    const serverDataStrings = serverData\n      .map((folder) => (typeof folder === \"string\" ? folder : folder.path))\n      .filter((folder): folder is string => typeof folder === \"string\" && !!folder);\n\n    const rootFolderPaths = rootFolders.map((folder) => (typeof folder === \"string\" ? folder : folder.path));\n\n    const rootFoldersSet = new Set(rootFolderPaths);\n    const serverDataSet = new Set(serverDataStrings);\n\n    const missingOnServer: InputConfigRootFolder[] = [];\n    const notAvailableAnymore: MergedRootFolderResource[] = [];\n\n    rootFolders.forEach((folder) => {\n      const folderPath = typeof folder === \"string\" ? folder : folder.path;\n      if (!serverDataSet.has(folderPath)) {\n        missingOnServer.push(folder);\n      }\n    });\n\n    serverData.forEach((folder) => {\n      const folderPath = typeof folder === \"string\" ? folder : folder.path;\n      if (folderPath && !rootFoldersSet.has(folderPath)) {\n        notAvailableAnymore.push(folder);\n      }\n    });\n\n    this.logger.debug({ missingOnServer, notAvailableAnymore }, \"Root folder comparison\");\n\n    if (missingOnServer.length === 0 && notAvailableAnymore.length === 0) {\n      this.logger.debug(`Root folders are in sync`);\n      return null;\n    }\n\n    this.logger.info(`Found ${missingOnServer.length + notAvailableAnymore.length} differences for root folders.`);\n\n    return {\n      missingOnServer,\n      notAvailableAnymore,\n      changed: [],\n    };\n  }\n\n  protected async loadRootFoldersFromServer(): Promise<MergedRootFolderResource[]> {\n    return super.loadRootFoldersFromServer();\n  }\n}\n"
  },
  {
    "path": "src/rootFolder/rootFolderLidarr.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { LidarrRootFolderSync } from \"./rootFolderLidarr\";\nimport { ServerCache } from \"../cache\";\nimport { InputConfigRootFolderLidarr } from \"../types/config.types\";\nimport { getSpecificClient } from \"../clients/unified-client\";\n\n// Mock the unified client\nvi.mock(\"../clients/unified-client\", () => ({\n  getSpecificClient: vi.fn(),\n}));\n\n// Mock the quality profiles loader\nvi.mock(\"../quality-profiles\", () => ({\n  loadQualityProfilesFromServer: vi.fn(),\n}));\n\nimport { loadQualityProfilesFromServer } from \"../quality-profiles\";\n\ndescribe(\"LidarrRootFolderSync\", () => {\n  const mockApi = {\n    getRootfolders: vi.fn(),\n    getMetadataProfiles: vi.fn(),\n    createTag: vi.fn(),\n  };\n\n  let serverCache: ServerCache;\n\n  beforeEach(() => {\n    vi.clearAllMocks();\n    (getSpecificClient as any).mockReturnValue(mockApi);\n    serverCache = new ServerCache([], [], [], []);\n    serverCache.tags = [];\n    vi.mocked(loadQualityProfilesFromServer).mockResolvedValue([\n      { id: 1, name: \"Any\" },\n      { id: 2, name: \"Lossless\" },\n    ]);\n    mockApi.getMetadataProfiles.mockResolvedValue([\n      { id: 10, name: \"Standard\" },\n      { id: 20, name: \"Enhanced\" },\n    ]);\n  });\n\n  describe(\"resolveRootFolderConfig\", () => {\n    it(\"should resolve Lidarr config with required fields\", async () => {\n      serverCache.tags = [\n        { id: 100, label: \"tag1\" },\n        { id: 200, label: \"tag2\" },\n      ];\n\n      const sync = new LidarrRootFolderSync();\n      const config: InputConfigRootFolderLidarr = {\n        path: \"/music\",\n        name: \"My Music\",\n        metadata_profile: \"Standard\",\n        quality_profile: \"Any\",\n      };\n\n      const result = await sync.resolveRootFolderConfig(config, serverCache);\n\n      expect(result).toEqual({\n        path: \"/music\",\n        name: \"My Music\",\n        defaultMetadataProfileId: 10,\n        defaultQualityProfileId: 1,\n        defaultTags: [],\n      });\n    });\n\n    it(\"should resolve Lidarr config with optional monitor fields\", async () => {\n      serverCache.tags = [];\n\n      const sync = new LidarrRootFolderSync();\n      const config: InputConfigRootFolderLidarr = {\n        path: \"/music\",\n        name: \"My Music\",\n        metadata_profile: \"Standard\",\n        quality_profile: \"Any\",\n        monitor: \"all\",\n        monitor_new_album: \"new\",\n      };\n\n      const result = await sync.resolveRootFolderConfig(config, serverCache);\n\n      expect(result).toEqual({\n        path: \"/music\",\n        name: \"My Music\",\n        defaultMetadataProfileId: 10,\n        defaultQualityProfileId: 1,\n        defaultMonitorOption: \"all\",\n        defaultNewItemMonitorOption: \"new\",\n        defaultTags: [],\n      });\n    });\n\n    it(\"should resolve Lidarr config with existing tags\", async () => {\n      serverCache.tags = [\n        { id: 100, label: \"tag1\" },\n        { id: 200, label: \"tag2\" },\n      ];\n\n      const sync = new LidarrRootFolderSync();\n      const config: InputConfigRootFolderLidarr = {\n        path: \"/music\",\n        name: \"My Music\",\n        metadata_profile: \"Standard\",\n        quality_profile: \"Any\",\n        tags: [\"tag1\", \"tag2\"],\n      };\n\n      const result = await sync.resolveRootFolderConfig(config, serverCache);\n\n      expect(result).toEqual({\n        path: \"/music\",\n        name: \"My Music\",\n        defaultMetadataProfileId: 10,\n        defaultQualityProfileId: 1,\n        defaultTags: [100, 200],\n      });\n    });\n\n    it(\"should throw error for missing metadata profile\", async () => {\n      serverCache.tags = [];\n\n      const sync = new LidarrRootFolderSync();\n      const config: InputConfigRootFolderLidarr = {\n        path: \"/music\",\n        name: \"My Music\",\n        metadata_profile: \"NonExistent\",\n        quality_profile: \"Any\",\n      };\n\n      await expect(sync.resolveRootFolderConfig(config, serverCache)).rejects.toThrow(\n        \"Metadata profile 'NonExistent' not found on Lidarr server\",\n      );\n    });\n\n    it(\"should throw error for missing quality profile\", async () => {\n      serverCache.tags = [];\n\n      const sync = new LidarrRootFolderSync();\n      const config: InputConfigRootFolderLidarr = {\n        path: \"/music\",\n        name: \"My Music\",\n        metadata_profile: \"Standard\",\n        quality_profile: \"NonExistent\",\n      };\n\n      await expect(sync.resolveRootFolderConfig(config, serverCache)).rejects.toThrow(\n        \"Quality profile 'NonExistent' not found on Lidarr server\",\n      );\n    });\n\n    it(\"should create missing tags\", async () => {\n      serverCache.tags = [{ id: 100, label: \"existing\" }];\n      mockApi.createTag.mockResolvedValue({ id: 300, label: \"nonexistent\" });\n\n      const sync = new LidarrRootFolderSync();\n      const config: InputConfigRootFolderLidarr = {\n        path: \"/music\",\n        name: \"My Music\",\n        metadata_profile: \"Standard\",\n        quality_profile: \"Any\",\n        tags: [\"existing\", \"nonexistent\"],\n      };\n\n      const result = await sync.resolveRootFolderConfig(config, serverCache);\n\n      expect(mockApi.createTag).toHaveBeenCalledWith({ label: \"nonexistent\" });\n      expect(result).toEqual({\n        path: \"/music\",\n        name: \"My Music\",\n        defaultMetadataProfileId: 10,\n        defaultQualityProfileId: 1,\n        defaultTags: [100, 300],\n      });\n      expect(serverCache.tags).toEqual([\n        { id: 100, label: \"existing\" },\n        { id: 300, label: \"nonexistent\" },\n      ]);\n    });\n  });\n\n  describe(\"calculateDiff\", () => {\n    it(\"should handle object root folders\", async () => {\n      mockApi.getRootfolders.mockResolvedValue([\"/existing\"]);\n\n      const sync = new LidarrRootFolderSync();\n      const result = await sync.calculateDiff(\n        [\n          { path: \"/existing\", name: \"existing\", metadata_profile: \"Standard\", quality_profile: \"Any\" },\n          { path: \"/new\", name: \"new\", metadata_profile: \"Standard\", quality_profile: \"Any\" },\n        ],\n        serverCache,\n      );\n\n      expect(result).toEqual({\n        missingOnServer: [{ path: \"/new\", name: \"new\", metadata_profile: \"Standard\", quality_profile: \"Any\" }],\n        notAvailableAnymore: [],\n        changed: [\n          { config: { path: \"/existing\", name: \"existing\", metadata_profile: \"Standard\", quality_profile: \"Any\" }, server: \"/existing\" },\n        ],\n      });\n    });\n\n    it(\"should handle server returning objects\", async () => {\n      mockApi.getRootfolders.mockResolvedValue([\n        { path: \"/server-folder\", id: 1, name: \"Server Folder\" },\n        { path: \"/old-server\", id: 2, name: \"Old Server\" },\n      ]);\n\n      const sync = new LidarrRootFolderSync();\n      const result = await sync.calculateDiff(\n        [\n          { path: \"/server-folder\", name: \"Config Folder\", metadata_profile: \"Standard\", quality_profile: \"Any\" },\n          { path: \"/new-config\", name: \"New Config\", metadata_profile: \"Standard\", quality_profile: \"Any\" },\n        ],\n        serverCache,\n      );\n\n      expect(result).toEqual({\n        missingOnServer: [{ path: \"/new-config\", name: \"New Config\", metadata_profile: \"Standard\", quality_profile: \"Any\" }],\n        notAvailableAnymore: [{ path: \"/old-server\", id: 2, name: \"Old Server\" }],\n        changed: [\n          {\n            config: { path: \"/server-folder\", name: \"Config Folder\", metadata_profile: \"Standard\", quality_profile: \"Any\" },\n            server: { path: \"/server-folder\", id: 1, name: \"Server Folder\" },\n          },\n        ],\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/rootFolder/rootFolderLidarr.ts",
    "content": "import {\n  MetadataProfileResource,\n  MonitorTypes,\n  NewItemMonitorTypes,\n  QualityProfileResource,\n  RootFolderResource,\n  TagResource,\n} from \"../__generated__/lidarr/data-contracts\";\nimport { ServerCache } from \"../cache\";\nimport { LidarrClient } from \"../clients/lidarr-client\";\nimport { getSpecificClient } from \"../clients/unified-client\";\nimport { loadQualityProfilesFromServer } from \"../quality-profiles\";\nimport { InputConfigRootFolderLidarr } from \"../types/config.types\";\nimport { compareObjectsCarr } from \"../util\";\nimport { RootFolderDiff } from \"./rootFolder.types\";\nimport { BaseRootFolderSync } from \"./rootFolderBase\";\n\nexport class LidarrRootFolderSync extends BaseRootFolderSync<InputConfigRootFolderLidarr> {\n  protected api: LidarrClient = getSpecificClient(\"LIDARR\");\n\n  protected getArrType(): \"LIDARR\" {\n    return \"LIDARR\";\n  }\n\n  public async resolveRootFolderConfig(config: InputConfigRootFolderLidarr, serverCache: ServerCache): Promise<RootFolderResource> {\n    if (typeof config === \"string\") {\n      throw new Error(`Lidarr root folders must be objects with name, metadata_profile, and quality_profile. Got string: ${config}`);\n    }\n\n    // Load quality profiles and metadata profiles for Lidarr\n    const [qualityProfiles, metadataProfiles] = await Promise.all([loadQualityProfilesFromServer(), this.api.getMetadataProfiles()]);\n\n    const qualityProfileMap = new Map<string, number>();\n    const metadataProfileMap = new Map<string, number>();\n\n    qualityProfiles.forEach((profile: QualityProfileResource) => {\n      if (profile.name && profile.id !== undefined) {\n        qualityProfileMap.set(profile.name, profile.id);\n      }\n    });\n\n    metadataProfiles.forEach((profile: MetadataProfileResource) => {\n      if (profile.id !== undefined && profile.name) {\n        metadataProfileMap.set(profile.name, profile.id);\n      }\n    });\n\n    const name = config.name;\n    const metadataProfileId = config.metadata_profile ? metadataProfileMap.get(config.metadata_profile) : undefined;\n    const qualityProfileId = config.quality_profile ? qualityProfileMap.get(config.quality_profile) : undefined;\n\n    if (config.metadata_profile && metadataProfileId === undefined) {\n      throw new Error(`Metadata profile '${config.metadata_profile}' not found on Lidarr server`);\n    }\n\n    if (config.quality_profile && qualityProfileId === undefined) {\n      throw new Error(`Quality profile '${config.quality_profile}' not found on Lidarr server`);\n    }\n\n    // Resolve tag names to IDs, creating tags if they don't exist\n    const newTags: TagResource[] = [];\n    const defaultTags = config.tags\n      ? await Promise.all(\n          config.tags.map(async (tagName) => {\n            const existingTag = serverCache.tags.find((tag) => tag.label === tagName);\n            if (existingTag) {\n              return existingTag.id;\n            } else {\n              // Tag doesn't exist, create it\n              const newTag = await this.api.createTag({ label: tagName });\n              newTags.push(newTag);\n              this.logger.info(`Created new tag '${tagName}' with ID ${newTag.id}`);\n              return newTag.id!;\n            }\n          }),\n        )\n      : [];\n\n    // Update serverCache with new tags\n    if (newTags.length > 0) {\n      serverCache.tags.push(...newTags);\n    }\n\n    const result: RootFolderResource = {\n      path: config.path,\n      name,\n      defaultMetadataProfileId: metadataProfileId,\n      defaultQualityProfileId: qualityProfileId,\n      defaultTags: defaultTags.filter((id: number | undefined): id is number => id !== undefined),\n    };\n\n    if (config.monitor) {\n      result.defaultMonitorOption = config.monitor as MonitorTypes;\n    }\n\n    if (config.monitor_new_album) {\n      result.defaultNewItemMonitorOption = config.monitor_new_album as NewItemMonitorTypes;\n    }\n\n    return result;\n  }\n\n  private isRootFolderConfigEqual(resolvedConfig: RootFolderResource, serverFolder: RootFolderResource): boolean {\n    // Only compare the configurable fields, filter out server-only fields like id, accessible, freeSpace, etc.\n    const configFields = {\n      name: resolvedConfig.name,\n      path: resolvedConfig.path,\n      defaultMetadataProfileId: resolvedConfig.defaultMetadataProfileId,\n      defaultQualityProfileId: resolvedConfig.defaultQualityProfileId,\n      defaultMonitorOption: resolvedConfig.defaultMonitorOption,\n      defaultNewItemMonitorOption: resolvedConfig.defaultNewItemMonitorOption,\n      defaultTags: resolvedConfig.defaultTags,\n    };\n\n    // For Lidarr, we know the server folder has the Lidarr-specific fields\n    const lidarrServerFolder = serverFolder as RootFolderResource & {\n      name?: string;\n      defaultMetadataProfileId?: number;\n      defaultQualityProfileId?: number;\n      defaultMonitorOption?: string;\n      defaultNewItemMonitorOption?: string;\n      defaultTags?: number[];\n    };\n\n    const serverFields = {\n      name: lidarrServerFolder.name,\n      path: lidarrServerFolder.path,\n      defaultMetadataProfileId: lidarrServerFolder.defaultMetadataProfileId,\n      defaultQualityProfileId: lidarrServerFolder.defaultQualityProfileId,\n      defaultMonitorOption: lidarrServerFolder.defaultMonitorOption,\n      defaultNewItemMonitorOption: lidarrServerFolder.defaultNewItemMonitorOption,\n      defaultTags: lidarrServerFolder.defaultTags,\n    };\n\n    return compareObjectsCarr(serverFields, configFields).equal;\n  }\n\n  async calculateDiff(\n    rootFolders: InputConfigRootFolderLidarr[],\n    serverCache: ServerCache,\n  ): Promise<RootFolderDiff<InputConfigRootFolderLidarr> | null> {\n    if (rootFolders == null) {\n      this.logger.debug(`Config 'root_folders' not specified. Ignoring.`);\n      return null;\n    }\n\n    const serverData = await this.loadRootFoldersFromServer();\n\n    // If config is empty array, all server folders should be removed\n    if (rootFolders.length === 0) {\n      const notAvailableAnymore = serverData.map((folder) => (typeof folder === \"string\" ? folder : folder));\n      this.logger.info(`Found ${notAvailableAnymore.length} differences for root folders.`);\n\n      return {\n        missingOnServer: [],\n        notAvailableAnymore,\n        changed: [],\n      };\n    }\n\n    const missingOnServer: InputConfigRootFolderLidarr[] = [];\n    const notAvailableAnymore: RootFolderResource[] = [];\n    const changed: Array<{ config: InputConfigRootFolderLidarr; server: RootFolderResource }> = [];\n\n    // Create maps for efficient lookup\n    const serverByPath = new Map<string, RootFolderResource>();\n    serverData.forEach((folder) => {\n      const path = typeof folder === \"string\" ? folder : folder.path;\n      if (path) {\n        serverByPath.set(path, folder);\n      }\n    });\n\n    // Process each config folder\n    for (const configFolder of rootFolders) {\n      const configPath = typeof configFolder === \"string\" ? configFolder : configFolder.path;\n      const serverFolder = serverByPath.get(configPath);\n\n      if (!serverFolder) {\n        // Folder doesn't exist on server\n        missingOnServer.push(configFolder);\n      } else {\n        // Folder exists, check if configuration matches\n        const resolvedConfig = await this.resolveRootFolderConfig(configFolder, serverCache);\n        const isChanged = !this.isRootFolderConfigEqual(\n          resolvedConfig,\n          typeof serverFolder === \"string\" ? { path: serverFolder } : serverFolder,\n        );\n        if (isChanged) {\n          changed.push({ config: configFolder, server: serverFolder });\n        }\n        // Remove from serverByPath so it won't be considered \"not available anymore\"\n        serverByPath.delete(configPath);\n      }\n    }\n\n    // Any remaining server folders are not in config\n    serverByPath.forEach((folder) => {\n      notAvailableAnymore.push(folder);\n    });\n\n    this.logger.debug({ missingOnServer, notAvailableAnymore, changed }, \"Root folder comparison\");\n\n    if (missingOnServer.length === 0 && notAvailableAnymore.length === 0 && changed.length === 0) {\n      this.logger.debug(`Root folders are in sync`);\n      return null;\n    }\n\n    this.logger.info(`Found ${missingOnServer.length + notAvailableAnymore.length + changed.length} differences for root folders.`);\n\n    return {\n      missingOnServer,\n      notAvailableAnymore,\n      changed,\n    };\n  }\n}\n"
  },
  {
    "path": "src/rootFolder/rootFolderReadarr.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { ReadarrRootFolderSync } from \"./rootFolderReadarr\";\nimport { getSpecificClient } from \"../clients/unified-client\";\nimport { ServerCache } from \"../cache\";\nimport { InputConfigRootFolderReadarr } from \"../types/config.types\";\n\n// Mock the unified client\nvi.mock(\"../clients/unified-client\", () => ({\n  getSpecificClient: vi.fn(),\n}));\n\n// Mock the quality profiles loader\nvi.mock(\"../quality-profiles\", () => ({\n  loadQualityProfilesFromServer: vi.fn(),\n}));\n\nimport { loadQualityProfilesFromServer } from \"../quality-profiles\";\n\ndescribe(\"ReadarrRootFolderSync\", () => {\n  const mockApi = {\n    getRootfolders: vi.fn(),\n    getMetadataProfiles: vi.fn(),\n    createTag: vi.fn(),\n  };\n\n  let serverCache: ServerCache;\n\n  beforeEach(() => {\n    vi.clearAllMocks();\n    (getSpecificClient as any).mockReturnValue(mockApi);\n    serverCache = new ServerCache([], [], [], []);\n    serverCache.tags = [];\n    vi.mocked(loadQualityProfilesFromServer).mockResolvedValue([\n      { id: 1, name: \"eBook\" },\n      { id: 2, name: \"Audiobook\" },\n    ]);\n    mockApi.getMetadataProfiles.mockResolvedValue([\n      { id: 10, name: \"Standard\" },\n      { id: 20, name: \"None\" },\n    ]);\n  });\n\n  describe(\"resolveRootFolderConfig\", () => {\n    it(\"should resolve Readarr config with required fields\", async () => {\n      serverCache.tags = [\n        { id: 100, label: \"tag1\" },\n        { id: 200, label: \"tag2\" },\n      ];\n\n      const sync = new ReadarrRootFolderSync();\n      const config: InputConfigRootFolderReadarr = {\n        path: \"/books\",\n        name: \"My Books\",\n        metadata_profile: \"Standard\",\n        quality_profile: \"eBook\",\n      };\n\n      const result = await sync.resolveRootFolderConfig(config, serverCache);\n\n      expect(result).toEqual({\n        path: \"/books\",\n        name: \"My Books\",\n        defaultMetadataProfileId: 10,\n        defaultQualityProfileId: 1,\n        defaultTags: [],\n      });\n    });\n\n    it(\"should resolve Readarr config with optional monitor fields\", async () => {\n      serverCache.tags = [];\n\n      const sync = new ReadarrRootFolderSync();\n      const config: InputConfigRootFolderReadarr = {\n        path: \"/books\",\n        name: \"My Books\",\n        metadata_profile: \"Standard\",\n        quality_profile: \"eBook\",\n        monitor: \"all\",\n        monitor_new_items: \"new\",\n      };\n\n      const result = await sync.resolveRootFolderConfig(config, serverCache);\n\n      expect(result).toEqual({\n        path: \"/books\",\n        name: \"My Books\",\n        defaultMetadataProfileId: 10,\n        defaultQualityProfileId: 1,\n        defaultMonitorOption: \"all\",\n        defaultNewItemMonitorOption: \"new\",\n        defaultTags: [],\n      });\n    });\n\n    it(\"should resolve Readarr config with existing tags\", async () => {\n      serverCache.tags = [\n        { id: 100, label: \"tag1\" },\n        { id: 200, label: \"tag2\" },\n      ];\n\n      const sync = new ReadarrRootFolderSync();\n      const config: InputConfigRootFolderReadarr = {\n        path: \"/books\",\n        name: \"My Books\",\n        metadata_profile: \"Standard\",\n        quality_profile: \"eBook\",\n        tags: [\"tag1\", \"tag2\"],\n      };\n\n      const result = await sync.resolveRootFolderConfig(config, serverCache);\n\n      expect(result).toEqual({\n        path: \"/books\",\n        name: \"My Books\",\n        defaultMetadataProfileId: 10,\n        defaultQualityProfileId: 1,\n        defaultTags: [100, 200],\n      });\n    });\n\n    it(\"should resolve Readarr config with Calibre integration\", async () => {\n      serverCache.tags = [];\n\n      const sync = new ReadarrRootFolderSync();\n      const config: InputConfigRootFolderReadarr = {\n        path: \"/books\",\n        name: \"Calibre Library\",\n        metadata_profile: \"Standard\",\n        quality_profile: \"eBook\",\n        is_calibre_library: true,\n        calibre_host: \"localhost\",\n        calibre_port: 8080,\n      };\n\n      const result = await sync.resolveRootFolderConfig(config, serverCache);\n\n      expect(result).toEqual({\n        path: \"/books\",\n        name: \"Calibre Library\",\n        defaultMetadataProfileId: 10,\n        defaultQualityProfileId: 1,\n        defaultTags: [],\n        isCalibreLibrary: true,\n        host: \"localhost\",\n        port: 8080,\n      });\n    });\n\n    it(\"should throw error for missing metadata profile\", async () => {\n      serverCache.tags = [];\n\n      const sync = new ReadarrRootFolderSync();\n      const config: InputConfigRootFolderReadarr = {\n        path: \"/books\",\n        name: \"My Books\",\n        metadata_profile: \"NonExistent\",\n        quality_profile: \"eBook\",\n      };\n\n      await expect(sync.resolveRootFolderConfig(config, serverCache)).rejects.toThrow(\n        \"Metadata profile 'NonExistent' not found on Readarr server\",\n      );\n    });\n\n    it(\"should throw error for missing quality profile\", async () => {\n      serverCache.tags = [];\n\n      const sync = new ReadarrRootFolderSync();\n      const config: InputConfigRootFolderReadarr = {\n        path: \"/books\",\n        name: \"My Books\",\n        metadata_profile: \"Standard\",\n        quality_profile: \"NonExistent\",\n      };\n\n      await expect(sync.resolveRootFolderConfig(config, serverCache)).rejects.toThrow(\n        \"Quality profile 'NonExistent' not found on Readarr server\",\n      );\n    });\n\n    it(\"should create missing tags\", async () => {\n      serverCache.tags = [{ id: 100, label: \"existing\" }];\n      mockApi.createTag.mockResolvedValue({ id: 300, label: \"nonexistent\" });\n\n      const sync = new ReadarrRootFolderSync();\n      const config: InputConfigRootFolderReadarr = {\n        path: \"/books\",\n        name: \"My Books\",\n        metadata_profile: \"Standard\",\n        quality_profile: \"eBook\",\n        tags: [\"existing\", \"nonexistent\"],\n      };\n\n      const result = await sync.resolveRootFolderConfig(config, serverCache);\n\n      expect(mockApi.createTag).toHaveBeenCalledWith({ label: \"nonexistent\" });\n      expect(result).toEqual({\n        path: \"/books\",\n        name: \"My Books\",\n        defaultMetadataProfileId: 10,\n        defaultQualityProfileId: 1,\n        defaultTags: [100, 300],\n      });\n      expect(serverCache.tags).toEqual([\n        { id: 100, label: \"existing\" },\n        { id: 300, label: \"nonexistent\" },\n      ]);\n    });\n  });\n\n  describe(\"calculateDiff\", () => {\n    it(\"should handle object root folders\", async () => {\n      mockApi.getRootfolders.mockResolvedValue([\"/existing\"]);\n\n      const sync = new ReadarrRootFolderSync();\n      const result = await sync.calculateDiff(\n        [\n          { path: \"/existing\", name: \"existing\", metadata_profile: \"Standard\", quality_profile: \"eBook\" },\n          { path: \"/new\", name: \"new\", metadata_profile: \"Standard\", quality_profile: \"eBook\" },\n        ],\n        serverCache,\n      );\n\n      expect(result).toEqual({\n        missingOnServer: [{ path: \"/new\", name: \"new\", metadata_profile: \"Standard\", quality_profile: \"eBook\" }],\n        notAvailableAnymore: [],\n        changed: [\n          { config: { path: \"/existing\", name: \"existing\", metadata_profile: \"Standard\", quality_profile: \"eBook\" }, server: \"/existing\" },\n        ],\n      });\n    });\n\n    it(\"should handle server returning objects\", async () => {\n      mockApi.getRootfolders.mockResolvedValue([\n        { path: \"/server-folder\", id: 1, name: \"Server Folder\" },\n        { path: \"/old-server\", id: 2, name: \"Old Server\" },\n      ]);\n\n      const sync = new ReadarrRootFolderSync();\n      const result = await sync.calculateDiff(\n        [\n          { path: \"/server-folder\", name: \"Config Folder\", metadata_profile: \"Standard\", quality_profile: \"eBook\" },\n          { path: \"/new-config\", name: \"New Config\", metadata_profile: \"Standard\", quality_profile: \"eBook\" },\n        ],\n        serverCache,\n      );\n\n      expect(result).toEqual({\n        missingOnServer: [{ path: \"/new-config\", name: \"New Config\", metadata_profile: \"Standard\", quality_profile: \"eBook\" }],\n        notAvailableAnymore: [{ path: \"/old-server\", id: 2, name: \"Old Server\" }],\n        changed: [\n          {\n            config: { path: \"/server-folder\", name: \"Config Folder\", metadata_profile: \"Standard\", quality_profile: \"eBook\" },\n            server: { path: \"/server-folder\", id: 1, name: \"Server Folder\" },\n          },\n        ],\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/rootFolder/rootFolderReadarr.ts",
    "content": "import {\n  MetadataProfileResource,\n  MonitorTypes,\n  NewItemMonitorTypes,\n  QualityProfileResource,\n  RootFolderResource,\n  TagResource,\n} from \"../__generated__/readarr/data-contracts\";\nimport { ServerCache } from \"../cache\";\nimport { getSpecificClient } from \"../clients/unified-client\";\nimport { loadQualityProfilesFromServer } from \"../quality-profiles\";\nimport { InputConfigRootFolderReadarr } from \"../types/config.types\";\nimport { compareObjectsCarr } from \"../util\";\nimport { RootFolderDiff } from \"./rootFolder.types\";\nimport { BaseRootFolderSync } from \"./rootFolderBase\";\n\nexport class ReadarrRootFolderSync extends BaseRootFolderSync<InputConfigRootFolderReadarr> {\n  protected api = getSpecificClient(\"READARR\");\n\n  protected getArrType(): \"READARR\" {\n    return \"READARR\";\n  }\n\n  public async resolveRootFolderConfig(config: InputConfigRootFolderReadarr, serverCache: ServerCache): Promise<RootFolderResource> {\n    if (typeof config === \"string\") {\n      throw new Error(`Readarr root folders must be objects with name, metadata_profile, and quality_profile. Got string: ${config}`);\n    }\n\n    // Load quality profiles and metadata profiles for Readarr\n    const [qualityProfiles, metadataProfiles] = await Promise.all([loadQualityProfilesFromServer(), this.api.getMetadataProfiles()]);\n\n    const qualityProfileMap = new Map<string, number>();\n    const metadataProfileMap = new Map<string, number>();\n\n    qualityProfiles.forEach((profile: QualityProfileResource) => {\n      if (profile.name && profile.id !== undefined) {\n        qualityProfileMap.set(profile.name, profile.id);\n      }\n    });\n\n    metadataProfiles.forEach((profile: MetadataProfileResource) => {\n      if (profile.id !== undefined && profile.name) {\n        metadataProfileMap.set(profile.name, profile.id);\n      }\n    });\n\n    const name = config.name;\n    const metadataProfileId = config.metadata_profile ? metadataProfileMap.get(config.metadata_profile) : undefined;\n    const qualityProfileId = config.quality_profile ? qualityProfileMap.get(config.quality_profile) : undefined;\n\n    if (config.metadata_profile && metadataProfileId === undefined) {\n      throw new Error(`Metadata profile '${config.metadata_profile}' not found on Readarr server`);\n    }\n\n    if (config.quality_profile && qualityProfileId === undefined) {\n      throw new Error(`Quality profile '${config.quality_profile}' not found on Readarr server`);\n    }\n\n    // Resolve tag names to IDs, creating tags if they don't exist\n    const newTags: TagResource[] = [];\n    const defaultTags = config.tags\n      ? await Promise.all(\n          config.tags.map(async (tagName) => {\n            const existingTag = serverCache.tags.find((tag) => tag.label === tagName);\n            if (existingTag) {\n              return existingTag.id;\n            } else {\n              // Tag doesn't exist, create it\n              const newTag = await this.api.createTag({ label: tagName });\n              newTags.push(newTag);\n              this.logger.info(`Created new tag '${tagName}' with ID ${newTag.id}`);\n              return newTag.id!;\n            }\n          }),\n        )\n      : [];\n\n    // Update serverCache with new tags\n    if (newTags.length > 0) {\n      serverCache.tags.push(...newTags);\n    }\n\n    const result: RootFolderResource = {\n      path: config.path,\n      name,\n      defaultMetadataProfileId: metadataProfileId,\n      defaultQualityProfileId: qualityProfileId,\n      defaultTags: defaultTags.filter((id: number | undefined): id is number => id !== undefined),\n    };\n\n    if (config.monitor) {\n      result.defaultMonitorOption = config.monitor as MonitorTypes;\n    }\n\n    if (config.monitor_new_items) {\n      result.defaultNewItemMonitorOption = config.monitor_new_items as NewItemMonitorTypes;\n    }\n\n    // Calibre integration fields (Readarr-specific)\n    if (config.is_calibre_library !== undefined) {\n      result.isCalibreLibrary = config.is_calibre_library;\n      result.host = config.calibre_host;\n      result.port = config.calibre_port;\n      result.urlBase = config.calibre_url_base;\n      result.username = config.calibre_username;\n      result.password = config.calibre_password;\n      result.library = config.calibre_library;\n      result.outputFormat = config.calibre_output_format;\n      result.outputProfile = config.calibre_output_profile;\n      result.useSsl = config.calibre_use_ssl;\n    }\n\n    return result;\n  }\n\n  private isRootFolderConfigEqual(resolvedConfig: RootFolderResource, serverFolder: RootFolderResource): boolean {\n    // Compare only configurable fields; server-only fields like id, accessible, freeSpace are excluded.\n    // Password is excluded since the API returns masked values, not the actual password.\n    const configFields = {\n      name: resolvedConfig.name,\n      path: resolvedConfig.path,\n      defaultMetadataProfileId: resolvedConfig.defaultMetadataProfileId,\n      defaultQualityProfileId: resolvedConfig.defaultQualityProfileId,\n      defaultMonitorOption: resolvedConfig.defaultMonitorOption,\n      defaultNewItemMonitorOption: resolvedConfig.defaultNewItemMonitorOption,\n      defaultTags: resolvedConfig.defaultTags,\n      // Calibre fields\n      isCalibreLibrary: resolvedConfig.isCalibreLibrary,\n      host: resolvedConfig.host,\n      port: resolvedConfig.port,\n      urlBase: resolvedConfig.urlBase,\n      username: resolvedConfig.username,\n      library: resolvedConfig.library,\n      outputFormat: resolvedConfig.outputFormat,\n      outputProfile: resolvedConfig.outputProfile,\n      useSsl: resolvedConfig.useSsl,\n    };\n\n    const serverFields = {\n      name: serverFolder.name,\n      path: serverFolder.path,\n      defaultMetadataProfileId: serverFolder.defaultMetadataProfileId,\n      defaultQualityProfileId: serverFolder.defaultQualityProfileId,\n      defaultMonitorOption: serverFolder.defaultMonitorOption,\n      defaultNewItemMonitorOption: serverFolder.defaultNewItemMonitorOption,\n      defaultTags: serverFolder.defaultTags,\n      // Calibre fields\n      isCalibreLibrary: serverFolder.isCalibreLibrary,\n      host: serverFolder.host,\n      port: serverFolder.port,\n      urlBase: serverFolder.urlBase,\n      username: serverFolder.username,\n      library: serverFolder.library,\n      outputFormat: serverFolder.outputFormat,\n      outputProfile: serverFolder.outputProfile,\n      useSsl: serverFolder.useSsl,\n    };\n\n    return compareObjectsCarr(serverFields, configFields).equal;\n  }\n\n  async calculateDiff(\n    rootFolders: InputConfigRootFolderReadarr[],\n    serverCache: ServerCache,\n  ): Promise<RootFolderDiff<InputConfigRootFolderReadarr> | null> {\n    if (rootFolders == null) {\n      this.logger.debug(`Config 'root_folders' not specified. Ignoring.`);\n      return null;\n    }\n\n    const serverData = await this.loadRootFoldersFromServer();\n\n    // If config is empty array, all server folders should be removed\n    if (rootFolders.length === 0) {\n      this.logger.info(`Found ${serverData.length} differences for root folders.`);\n\n      return {\n        missingOnServer: [],\n        notAvailableAnymore: serverData,\n        changed: [],\n      };\n    }\n\n    const missingOnServer: InputConfigRootFolderReadarr[] = [];\n    const notAvailableAnymore: RootFolderResource[] = [];\n    const changed: Array<{ config: InputConfigRootFolderReadarr; server: RootFolderResource }> = [];\n\n    // Create maps for efficient lookup\n    const serverByPath = new Map<string, RootFolderResource>();\n    serverData.forEach((folder) => {\n      const path = typeof folder === \"string\" ? folder : folder.path;\n      if (path) {\n        serverByPath.set(path, folder);\n      }\n    });\n\n    // Process each config folder\n    for (const configFolder of rootFolders) {\n      const configPath = typeof configFolder === \"string\" ? configFolder : configFolder.path;\n      const serverFolder = serverByPath.get(configPath);\n\n      if (!serverFolder) {\n        // Folder doesn't exist on server\n        missingOnServer.push(configFolder);\n      } else {\n        // Folder exists, check if configuration matches\n        const resolvedConfig = await this.resolveRootFolderConfig(configFolder, serverCache);\n        const isChanged = !this.isRootFolderConfigEqual(\n          resolvedConfig,\n          typeof serverFolder === \"string\" ? { path: serverFolder } : serverFolder,\n        );\n        if (isChanged) {\n          changed.push({ config: configFolder, server: serverFolder });\n        }\n        // Remove from serverByPath so it won't be considered \"not available anymore\"\n        serverByPath.delete(configPath);\n      }\n    }\n\n    // Any remaining server folders are not in config\n    serverByPath.forEach((folder) => {\n      notAvailableAnymore.push(folder);\n    });\n\n    this.logger.debug({ missingOnServer, notAvailableAnymore, changed }, \"Root folder comparison\");\n\n    if (missingOnServer.length === 0 && notAvailableAnymore.length === 0 && changed.length === 0) {\n      this.logger.debug(`Root folders are in sync`);\n      return null;\n    }\n\n    this.logger.info(`Found ${missingOnServer.length + notAvailableAnymore.length + changed.length} differences for root folders.`);\n\n    return {\n      missingOnServer,\n      notAvailableAnymore,\n      changed,\n    };\n  }\n}\n"
  },
  {
    "path": "src/rootFolder/rootFolderSyncer.ts",
    "content": "import { ServerCache } from \"../cache\";\nimport { ArrType } from \"../types/common.types\";\nimport { InputConfigRootFolder } from \"../types/config.types\";\nimport { RootFolderSyncResult } from \"./rootFolder.types\";\nimport { BaseRootFolderSync, GenericRootFolderSync } from \"./rootFolderBase\";\nimport { LidarrRootFolderSync } from \"./rootFolderLidarr\";\nimport { ReadarrRootFolderSync } from \"./rootFolderReadarr\";\n\nexport function createRootFolderSync(arrType: ArrType): BaseRootFolderSync {\n  switch (arrType) {\n    case \"LIDARR\":\n      return new LidarrRootFolderSync();\n    case \"READARR\":\n      return new ReadarrRootFolderSync();\n    default:\n      return new GenericRootFolderSync(arrType);\n  }\n}\n\nexport async function syncRootFolders(\n  arrType: ArrType,\n  rootFolders: InputConfigRootFolder[] | undefined,\n  serverCache: ServerCache,\n): Promise<RootFolderSyncResult> {\n  if (!rootFolders) {\n    return { added: 0, removed: 0, updated: 0 };\n  }\n\n  const sync = createRootFolderSync(arrType);\n  return sync.syncRootFolders(rootFolders, serverCache);\n}\n"
  },
  {
    "path": "src/tags.ts",
    "content": "import { getUnifiedClient } from \"./clients/unified-client\";\nimport { logger } from \"./logger\";\nimport { InputConfigDelayProfile } from \"./types/config.types\";\nimport { MergedDelayProfileResource, MergedTagResource } from \"./types/merged.types\";\nimport { getEnvs } from \"./env\";\n\nexport const loadServerTags = async (): Promise<MergedTagResource[]> => {\n  if (getEnvs().LOAD_LOCAL_SAMPLES) {\n    throw new Error(\"Local sample loading for tags is not implemented yet.\");\n  }\n  const api = getUnifiedClient();\n  const serverObjects = await api.getTags();\n  return serverObjects;\n};\n"
  },
  {
    "path": "src/telemetry.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { Telemetry, getTelemetryInstance, resetTelemetryInstance } from \"./telemetry\";\nimport { getEnvs } from \"./env\";\nimport { ArrType } from \"./types/common.types\";\nimport { InputConfigArrInstance } from \"./types/config.types\";\n\n// Mock the env\nvi.mock(\"./env\", () => ({\n  getEnvs: vi.fn(),\n}));\n\n// Mock logger\nvi.mock(\"./logger\", () => ({\n  logger: {\n    debug: vi.fn(),\n    info: vi.fn(),\n  },\n}));\n\n// Mock telemetry singleton\nvi.mock(\"./telemetry\", async () => {\n  const actual = await vi.importActual(\"./telemetry\");\n  return {\n    ...actual,\n    getTelemetryInstance: vi.fn(),\n  };\n});\n\ndescribe(\"Telemetry\", () => {\n  beforeEach(() => {\n    vi.clearAllMocks();\n    resetTelemetryInstance();\n  });\n\n  describe(\"constructor\", () => {\n    it(\"should initialize with telemetry disabled by default\", () => {\n      vi.mocked(getEnvs).mockReturnValue({\n        CONFIGARR_VERSION: \"1.0.0\",\n      } as any); // TELEMETRY_ENABLED not set\n\n      const telemetryInstance = new Telemetry();\n      expect(telemetryInstance).toBeDefined();\n    });\n\n    it(\"should initialize with telemetry enabled when configured via env\", () => {\n      vi.mocked(getEnvs).mockReturnValue({\n        TELEMETRY_ENABLED: true,\n        CONFIGARR_VERSION: \"1.0.0\",\n      } as any);\n\n      const telemetryInstance = new Telemetry();\n      expect(telemetryInstance).toBeDefined();\n    });\n\n    it(\"should initialize with telemetry disabled when configured via env\", () => {\n      vi.mocked(getEnvs).mockReturnValue({\n        TELEMETRY_ENABLED: false,\n        CONFIGARR_VERSION: \"1.0.0\",\n      } as any);\n\n      const telemetryInstance = new Telemetry();\n      expect(telemetryInstance).toBeDefined();\n    });\n\n    it(\"should initialize with telemetry enabled when configured via config\", () => {\n      vi.mocked(getEnvs).mockReturnValue({\n        CONFIGARR_VERSION: \"1.0.0\",\n      } as any); // TELEMETRY_ENABLED not set\n\n      const telemetryInstance = new Telemetry({ enabled: true });\n      expect(telemetryInstance).toBeDefined();\n    });\n\n    it(\"should prioritize env var over config\", () => {\n      vi.mocked(getEnvs).mockReturnValue({\n        TELEMETRY_ENABLED: false, // env says false\n        CONFIGARR_VERSION: \"1.0.0\",\n      } as any);\n\n      const telemetryInstance = new Telemetry({ enabled: true }); // config says true, but env takes precedence\n      expect(telemetryInstance).toBeDefined();\n    });\n\n    it(\"should use config when env is not set\", () => {\n      vi.mocked(getEnvs).mockReturnValue({\n        CONFIGARR_VERSION: \"1.0.0\",\n      } as any); // TELEMETRY_ENABLED not set\n\n      const telemetryInstance = new Telemetry({ enabled: true }); // config says true\n      expect(telemetryInstance).toBeDefined();\n    });\n  });\n\n  describe(\"trackFeatureUsage\", () => {\n    it(\"should not track when telemetry is disabled via env\", () => {\n      vi.mocked(getEnvs).mockReturnValue({\n        TELEMETRY_ENABLED: false,\n        CONFIGARR_VERSION: \"1.0.0\",\n      } as any);\n\n      const telemetryInstance = new Telemetry();\n      const globalConfig = {};\n      const instances = {\n        SONARR: [],\n        RADARR: [],\n        WHISPARR: [],\n        READARR: [],\n        LIDARR: [],\n      };\n\n      telemetryInstance.trackFeatureUsage(globalConfig as any, instances);\n\n      // Should not throw or do anything\n      expect(true).toBe(true);\n    });\n\n    it(\"should not track when config is disabled\", () => {\n      vi.mocked(getEnvs).mockReturnValue({\n        CONFIGARR_VERSION: \"1.0.0\",\n      } as any); // TELEMETRY_ENABLED not set\n\n      const telemetryInstance = new Telemetry({ enabled: false });\n      const globalConfig = {};\n      const instances = {\n        SONARR: [],\n        RADARR: [],\n        WHISPARR: [],\n        READARR: [],\n        LIDARR: [],\n      };\n\n      telemetryInstance.trackFeatureUsage(globalConfig as any, instances);\n\n      // Should not throw or do anything\n      expect(true).toBe(true);\n    });\n\n    it(\"should collect telemetry data correctly\", () => {\n      vi.mocked(getEnvs).mockReturnValue({\n        TELEMETRY_ENABLED: true,\n        CONFIGARR_VERSION: \"1.0.0\",\n      } as any);\n\n      const telemetryInstance = new Telemetry();\n      const globalConfig = {\n        customFormatDefinitions: [],\n        enableFullGitClone: true,\n      };\n\n      const instances: Record<ArrType, InputConfigArrInstance[]> = {\n        SONARR: [\n          {\n            base_url: \"http://localhost:8989\",\n            api_key: \"key\",\n            custom_formats: [{ trash_ids: [\"id1\"] }],\n            custom_format_groups: [\n              {\n                trash_guide: [{ id: \"group1\" }],\n                assign_scores_to: [{ name: \"profile1\", score: 10 }],\n              },\n            ],\n            quality_profiles: [\n              {\n                name: \"test\",\n                upgrade: { allowed: true, until_quality: \"test\", until_score: 100 },\n                min_format_score: 0,\n                score_set: \"default\",\n                quality_sort: \"top\",\n                qualities: [],\n              },\n            ],\n            delete_unmanaged_custom_formats: { enabled: true },\n            delete_unmanaged_quality_profiles: { enabled: true },\n            media_management: {},\n            root_folders: [\"folder1\"],\n            delay_profiles: { default: {} },\n            include: [\n              { template: \"recyclarr-template\", source: \"RECYCLARR\" },\n              { template: \"trash-template\", source: \"TRASH\" },\n            ],\n          },\n        ],\n        RADARR: [],\n        WHISPARR: [],\n        READARR: [],\n        LIDARR: [],\n      };\n\n      // Access private method for testing\n      const data = (telemetryInstance as any).collectTelemetryData(globalConfig, instances, false);\n\n      expect(data).toEqual({\n        version: \"1.0.0\",\n        sonarr_enabled: true,\n        radarr_enabled: true,\n        whisparr_enabled: true,\n        readarr_enabled: true,\n        lidarr_enabled: true,\n        recyclarr_templates: true,\n        trash_guide_templates: true,\n        local_templates: false,\n        local_custom_formats_path: false,\n        local_config_templates_path: false,\n        custom_formats: true,\n        custom_format_groups: true,\n        custom_format_group_scores: true,\n        delete_unmanaged_custom_formats: true,\n        custom_format_definitions: true,\n        quality_profiles: true,\n        quality_definition: false,\n        rename_quality_profiles: false,\n        clone_quality_profiles: false,\n        delete_unmanaged_quality_profiles: true,\n        media_management: true,\n        media_naming: false,\n        media_naming_api: false,\n        root_folders: true,\n        delay_profiles: true,\n        enable_full_git_clone: true,\n\n        sonarr_instances: 0,\n        radarr_instances: 0,\n        whisparr_instances: 0,\n        readarr_instances: 0,\n        lidarr_instances: 0,\n        total_instances: 0,\n        local_template_count: 0,\n      });\n    });\n\n    it(\"should only track once\", () => {\n      vi.mocked(getEnvs).mockReturnValue({\n        TELEMETRY_ENABLED: true,\n        CONFIGARR_VERSION: \"1.0.0\",\n      } as any);\n\n      const telemetryInstance = new Telemetry();\n      const globalConfig = {};\n      const instances: Record<ArrType, InputConfigArrInstance[]> = {\n        SONARR: [],\n        RADARR: [],\n        WHISPARR: [],\n        READARR: [],\n        LIDARR: [],\n      };\n\n      // First call should track\n      telemetryInstance.trackFeatureUsage(globalConfig as any, instances);\n\n      // Second call should not track again\n      telemetryInstance.trackFeatureUsage(globalConfig as any, instances);\n\n      expect(true).toBe(true); // Just verify no errors\n    });\n  });\n});\n"
  },
  {
    "path": "src/telemetry.ts",
    "content": "import { getEnvs } from \"./env\";\nimport { logger } from \"./logger\";\nimport { ArrType } from \"./types/common.types\";\nimport { InputConfigSchema, InputConfigArrInstance, MergedConfigInstance } from \"./types/config.types\";\nimport ky from \"ky\";\n\nexport interface TelemetryConfig {\n  enabled?: boolean;\n}\n\nexport interface TelemetryData {\n  // Version info\n  version: string;\n\n  // Global Arr type configuration\n  sonarr_enabled: boolean;\n  radarr_enabled: boolean;\n  whisparr_enabled: boolean;\n  readarr_enabled: boolean;\n  lidarr_enabled: boolean;\n\n  // Template sources\n  recyclarr_templates: boolean;\n  trash_guide_templates: boolean;\n  local_templates: boolean;\n\n  // Local paths usage\n  local_custom_formats_path: boolean;\n  local_config_templates_path: boolean;\n\n  // Custom format features\n  custom_formats: boolean;\n  custom_format_groups: boolean;\n  custom_format_group_scores: boolean;\n  delete_unmanaged_custom_formats: boolean;\n  custom_format_definitions: boolean;\n\n  // Quality profile features\n  quality_profiles: boolean;\n  quality_definition: boolean;\n  rename_quality_profiles: boolean;\n  clone_quality_profiles: boolean;\n  delete_unmanaged_quality_profiles: boolean;\n\n  // Media management features\n  media_management: boolean;\n  media_naming: boolean;\n  media_naming_api: boolean;\n\n  // Root folder management\n  root_folders: boolean;\n\n  // Delay profiles\n  delay_profiles: boolean;\n\n  // Experimental features\n  enable_full_git_clone: boolean;\n\n  // Instance statistics by arr type\n  sonarr_instances: number;\n  radarr_instances: number;\n  whisparr_instances: number;\n  readarr_instances: number;\n  lidarr_instances: number;\n  total_instances: number;\n\n  // Template usage counts\n  local_template_count: number;\n}\n\nlet telemetryInstance: Telemetry | null = null;\n\nexport class Telemetry {\n  private hasTracked: boolean = false;\n  private telemetryData: Partial<TelemetryData> | null = null;\n  private static isEnabledCache: boolean | null = null;\n\n  public static isEnabled(config?: TelemetryConfig): boolean {\n    if (this.isEnabledCache == null) {\n      this.isEnabledCache = getEnvs().TELEMETRY_ENABLED ?? config?.enabled ?? false;\n    }\n\n    return this.isEnabledCache;\n  }\n\n  constructor(config?: TelemetryConfig) {\n    // Environment variable takes precedence, then config file setting\n    if (Telemetry.isEnabled(config)) {\n      logger.info(\"Telemetry enabled - Thank you for helping improve Configarr!\");\n    }\n  }\n\n  private async track(eventName: string, data: TelemetryData) {\n    if (!Telemetry.isEnabledCache || this.hasTracked) {\n      return;\n    }\n\n    try {\n      // disable custom umami for now\n      // const umami = new Umami({\n      //   websiteId: \"6b0669cc-8047-4382-a551-95e1a6e92d42\",\n      //   hostUrl: \"https://telemetry.configarr.de\",\n      // });\n\n      // // Cast to any to handle external API type requirements\n      // await umami.track(eventName, data as any);\n\n      await ky.post(\"https://eu.i.posthog.com/capture/\", {\n        body: JSON.stringify({\n          api_key: \"phc_So3UZ2BxlK56T2UDPtVwMqPZ0F1XOQNOhHss2JNqMKF\", // gitleaks:allow\n          event: eventName,\n          properties: {\n            distinct_id: \"anonymous\",\n            ...data,\n          },\n        }),\n        timeout: 1000,\n      });\n    } catch (error) {\n      logger.debug(`Telemetry tracking failed (maybe AdBlocker). Ignoring.`);\n\n      if (getEnvs().LOG_STACKTRACE) {\n        logger.debug(error);\n      }\n    }\n  }\n\n  public trackFeatureUsage(globalConfig: InputConfigSchema, instances: Record<ArrType, InputConfigArrInstance[]>): void {\n    if (!Telemetry.isEnabledCache || this.hasTracked) {\n      return;\n    }\n\n    this.telemetryData = this.collectTelemetryData(globalConfig, instances);\n  }\n\n  public trackInstanceConfig(instanceConfig: MergedConfigInstance, arrType: ArrType): void {\n    if (!Telemetry.isEnabledCache || this.hasTracked || !this.telemetryData) {\n      return;\n    }\n\n    // Update telemetry data based on merged instance config\n    this.updateTelemetryFromInstance(instanceConfig);\n\n    // Increment instance count for this arr type\n    const data = this.telemetryData! as TelemetryData;\n    switch (arrType) {\n      case \"SONARR\":\n        data.sonarr_instances++;\n        break;\n      case \"RADARR\":\n        data.radarr_instances++;\n        break;\n      case \"WHISPARR\":\n        data.whisparr_instances++;\n        break;\n      case \"READARR\":\n        data.readarr_instances++;\n        break;\n      case \"LIDARR\":\n        data.lidarr_instances++;\n        break;\n    }\n    data.total_instances++;\n  }\n\n  public async finalizeTracking(): Promise<void> {\n    if (!Telemetry.isEnabledCache || this.hasTracked || !this.telemetryData) {\n      return;\n    }\n\n    try {\n      await this.track(\"feature_usage\", this.telemetryData as TelemetryData);\n    } catch (error) {\n      logger.debug(`Telemetry finalization failed: ${error}`);\n    }\n    this.hasTracked = true;\n  }\n\n  private updateTelemetryFromInstance(instanceConfig: MergedConfigInstance): void {\n    if (!this.telemetryData) return;\n\n    // Update all feature usage flags based on the merged instance config\n\n    // Custom format features\n    if (!this.telemetryData.custom_formats && instanceConfig.custom_formats && instanceConfig.custom_formats.length > 0) {\n      this.telemetryData.custom_formats = true;\n    }\n    if (!this.telemetryData.custom_format_groups && instanceConfig.custom_format_groups && instanceConfig.custom_format_groups.length > 0) {\n      this.telemetryData.custom_format_groups = true;\n      // Check for custom format group scores\n      if (\n        !this.telemetryData.custom_format_group_scores &&\n        instanceConfig.custom_format_groups.some((group) => group.assign_scores_to?.some((assign) => assign.score !== undefined))\n      ) {\n        this.telemetryData.custom_format_group_scores = true;\n      }\n    }\n    if (!this.telemetryData.delete_unmanaged_custom_formats && instanceConfig.delete_unmanaged_custom_formats?.enabled) {\n      this.telemetryData.delete_unmanaged_custom_formats = true;\n    }\n\n    // Quality profile features\n    if (!this.telemetryData.quality_profiles && instanceConfig.quality_profiles && instanceConfig.quality_profiles.length > 0) {\n      this.telemetryData.quality_profiles = true;\n    }\n    if (!this.telemetryData.quality_definition && instanceConfig.quality_definition) {\n      this.telemetryData.quality_definition = true;\n    }\n    if (\n      !this.telemetryData.rename_quality_profiles &&\n      instanceConfig.renameQualityProfiles &&\n      instanceConfig.renameQualityProfiles.length > 0\n    ) {\n      this.telemetryData.rename_quality_profiles = true;\n    }\n    if (\n      !this.telemetryData.clone_quality_profiles &&\n      instanceConfig.cloneQualityProfiles &&\n      instanceConfig.cloneQualityProfiles.length > 0\n    ) {\n      this.telemetryData.clone_quality_profiles = true;\n    }\n\n    if (!this.telemetryData.delete_unmanaged_quality_profiles && instanceConfig.delete_unmanaged_quality_profiles?.enabled) {\n      this.telemetryData.delete_unmanaged_quality_profiles = true;\n    }\n\n    // Media management features\n    if (!this.telemetryData.media_management && instanceConfig.media_management) {\n      this.telemetryData.media_management = true;\n    }\n    if (!this.telemetryData.media_naming && instanceConfig.media_naming) {\n      this.telemetryData.media_naming = true;\n    }\n    if (!this.telemetryData.media_naming_api && instanceConfig.media_naming_api) {\n      this.telemetryData.media_naming_api = true;\n    }\n\n    // Root folder management\n    if (!this.telemetryData.root_folders && instanceConfig.root_folders && instanceConfig.root_folders.length > 0) {\n      this.telemetryData.root_folders = true;\n    }\n\n    // Delay profiles\n    if (!this.telemetryData.delay_profiles && instanceConfig.delay_profiles) {\n      this.telemetryData.delay_profiles = true;\n    }\n  }\n\n  private collectTelemetryData(globalConfig: InputConfigSchema, instances: Record<ArrType, InputConfigArrInstance[]>): TelemetryData {\n    const arrTypes = Object.keys(instances) as ArrType[];\n    const allInstances = Object.values(instances).flat();\n\n    // Count template sources\n    let recyclarrTemplateCount = 0;\n    let trashGuideTemplateCount = 0;\n    let localTemplateCount = 0;\n\n    for (const instance of allInstances) {\n      if (instance.include) {\n        for (const include of instance.include) {\n          if (include.source === \"RECYCLARR\") {\n            recyclarrTemplateCount++;\n          } else if (include.source === \"TRASH\") {\n            trashGuideTemplateCount++;\n          } else {\n            localTemplateCount++;\n          }\n        }\n      }\n    }\n\n    // Check for custom format group scores\n    let usesCustomFormatGroupScores = false;\n    for (const instance of allInstances) {\n      if (instance.custom_format_groups) {\n        for (const group of instance.custom_format_groups) {\n          if (group.assign_scores_to?.some((assign) => assign.score !== undefined)) {\n            usesCustomFormatGroupScores = true;\n            break;\n          }\n        }\n        if (usesCustomFormatGroupScores) break;\n      }\n    }\n\n    return {\n      version: getEnvs().CONFIGARR_VERSION || \"unknown\",\n\n      // Global Arr type configuration\n      sonarr_enabled: globalConfig.sonarrEnabled !== false,\n      radarr_enabled: globalConfig.radarrEnabled !== false,\n      whisparr_enabled: globalConfig.whisparrEnabled !== false,\n      readarr_enabled: globalConfig.readarrEnabled !== false,\n      lidarr_enabled: globalConfig.lidarrEnabled !== false,\n\n      recyclarr_templates: recyclarrTemplateCount > 0,\n      trash_guide_templates: trashGuideTemplateCount > 0,\n      local_templates: localTemplateCount > 0,\n\n      // Local paths usage\n      local_custom_formats_path: globalConfig.localCustomFormatsPath !== undefined,\n      local_config_templates_path: globalConfig.localConfigTemplatesPath !== undefined,\n\n      custom_formats: allInstances.some((i) => i.custom_formats && i.custom_formats.length > 0),\n      custom_format_groups: allInstances.some((i) => i.custom_format_groups && i.custom_format_groups.length > 0),\n      custom_format_group_scores: usesCustomFormatGroupScores,\n      delete_unmanaged_custom_formats: allInstances.some((i) => i.delete_unmanaged_custom_formats?.enabled),\n      custom_format_definitions: globalConfig.customFormatDefinitions !== undefined,\n\n      quality_profiles: allInstances.some((i) => i.quality_profiles && i.quality_profiles.length > 0),\n      quality_definition: allInstances.some((i) => i.quality_definition !== undefined),\n      rename_quality_profiles: allInstances.some((i) => i.renameQualityProfiles && i.renameQualityProfiles.length > 0),\n      clone_quality_profiles: allInstances.some((i) => i.cloneQualityProfiles && i.cloneQualityProfiles.length > 0),\n      delete_unmanaged_quality_profiles: allInstances.some((i) => i.delete_unmanaged_quality_profiles?.enabled),\n\n      media_management: allInstances.some((i) => i.media_management !== undefined),\n      media_naming: allInstances.some((i) => i.media_naming !== undefined),\n      media_naming_api: allInstances.some((i) => i.media_naming_api !== undefined),\n\n      root_folders: allInstances.some((i) => i.root_folders && i.root_folders.length > 0),\n\n      delay_profiles: allInstances.some((i) => i.delay_profiles !== undefined),\n\n      enable_full_git_clone: globalConfig.enableFullGitClone === true,\n\n      sonarr_instances: 0,\n      radarr_instances: 0,\n      whisparr_instances: 0,\n      readarr_instances: 0,\n      lidarr_instances: 0,\n      total_instances: 0,\n\n      local_template_count: localTemplateCount,\n    };\n  }\n}\n\nexport function getTelemetryInstance(config?: TelemetryConfig): Telemetry {\n  if (telemetryInstance == null) {\n    telemetryInstance = new Telemetry(config);\n  }\n  return telemetryInstance;\n}\n\n// For testing: reset the singleton instance\nexport function resetTelemetryInstance(): void {\n  telemetryInstance = null;\n}\n"
  },
  {
    "path": "src/trash-guide.test.ts",
    "content": "import fs from \"node:fs\";\nimport { beforeEach, describe, expect, test, vi } from \"vitest\";\nimport {\n  loadAllQDsFromTrash,\n  loadQPFromTrash,\n  loadTrashCFConflicts,\n  transformTrashCFGroups,\n  transformTrashQDs,\n  transformTrashQPCFGroups,\n} from \"./trash-guide\";\nimport { InputConfigCustomFormatGroup } from \"./types/config.types\";\nimport { TrashCFGroupMapping, TrashQualityDefinition, TrashQP } from \"./types/trashguide.types\";\nimport * as util from \"./util\";\n\ndescribe(\"TrashGuide\", async () => {\n  beforeEach(() => {\n    vi.restoreAllMocks();\n  });\n\n  describe(\"loadAllQDsFromTrash\", () => {\n    test(\"should return a Map instance for a valid arrType\", async () => {\n      const mockQD: TrashQualityDefinition = {\n        trash_id: \"aed34b9f60ee115dfa7918b742336277\",\n        type: \"movie\",\n        qualities: [{ quality: \"SDTV\", min: 2, preferred: 95, max: 100 }],\n      };\n\n      vi.spyOn(fs, \"readdirSync\").mockReturnValue([\"movie.json\"] as any);\n      vi.spyOn(util, \"loadJsonFile\").mockReturnValueOnce(mockQD);\n\n      const result = await loadAllQDsFromTrash(\"RADARR\");\n\n      expect(result).toBeInstanceOf(Map);\n      expect(result.size).toBe(1);\n      expect(result.get(\"aed34b9f60ee115dfa7918b742336277\")).toEqual(mockQD);\n    });\n\n    test(\"should return an empty map when the directory doesn't exist\", async () => {\n      vi.spyOn(fs, \"readdirSync\").mockImplementation(() => {\n        throw new Error(\"ENOENT: no such file or directory\");\n      });\n\n      const result = await loadAllQDsFromTrash(\"RADARR\");\n\n      expect(result).toBeInstanceOf(Map);\n      expect(result.size).toBe(0);\n    });\n\n    test(\"skips single bad file and loads the rest\", async () => {\n      const mockQD: TrashQualityDefinition = {\n        trash_id: \"id-anime\",\n        type: \"anime\",\n        qualities: [],\n      };\n      vi.spyOn(fs, \"readdirSync\").mockReturnValue([\"movie.json\", \"anime.json\"] as any);\n      vi.spyOn(util, \"loadJsonFile\")\n        .mockImplementationOnce(() => {\n          throw new Error(\"parse error\");\n        })\n        .mockReturnValueOnce(mockQD);\n      const result = await loadAllQDsFromTrash(\"RADARR\");\n      expect(result.size).toBe(1);\n      expect(result.get(\"id-anime\")).toEqual(mockQD);\n    });\n\n    test(\"returns empty map when all loadJsonFile calls throw\", async () => {\n      vi.spyOn(fs, \"readdirSync\").mockReturnValue([\"movie.json\"] as any);\n      vi.spyOn(util, \"loadJsonFile\").mockImplementation(() => {\n        throw new Error(\"parse error\");\n      });\n      const result = await loadAllQDsFromTrash(\"RADARR\");\n      expect(result.size).toBe(0);\n    });\n\n    test(\"should load multiple QD files and key by trash_id\", async () => {\n      const mockQD1: TrashQualityDefinition = {\n        trash_id: \"id-movie\",\n        type: \"movie\",\n        qualities: [],\n      };\n      const mockQD2: TrashQualityDefinition = {\n        trash_id: \"id-anime\",\n        type: \"anime\",\n        qualities: [],\n      };\n\n      vi.spyOn(fs, \"readdirSync\").mockReturnValue([\"movie.json\", \"anime.json\"] as any);\n      vi.spyOn(util, \"loadJsonFile\").mockReturnValueOnce(mockQD1).mockReturnValueOnce(mockQD2);\n\n      const result = await loadAllQDsFromTrash(\"RADARR\");\n\n      expect(result).toBeInstanceOf(Map);\n      expect(result.size).toBe(2);\n      expect(result.get(\"id-movie\")).toEqual(mockQD1);\n      expect(result.get(\"id-anime\")).toEqual(mockQD2);\n    });\n  });\n\n  test(\"loadQPFromTrash - normal\", async ({}) => {\n    const results = await loadQPFromTrash(\"RADARR\");\n\n    console.log(results.keys());\n  });\n\n  test(\"transformTrashQDs - diff preferred size with ratio\", async ({}) => {\n    const trashQualityDef = {\n      trash_id: \"aed34b9f60ee115dfa7918b742336277\",\n      type: \"movie\",\n      qualities: [\n        {\n          quality: \"SDTV\",\n          min: 2,\n          preferred: 95,\n          max: 100,\n        },\n      ],\n    };\n\n    const clone: TrashQualityDefinition = JSON.parse(JSON.stringify(trashQualityDef));\n\n    const result = transformTrashQDs(clone, 0.5);\n\n    expect(result[0]!.preferred).toBe(95);\n\n    const resultLow = transformTrashQDs(clone, 0.0);\n    expect(resultLow[0]!.preferred).toBe(2);\n\n    const resultHigh = transformTrashQDs(clone, 1.0);\n    expect(resultHigh[0]!.preferred).toBe(100);\n  });\n\n  test(\"transformTrashQDs - diff preferred size with ratio, ignore if out of range\", async ({}) => {\n    const trashQualityDef = {\n      trash_id: \"aed34b9f60ee115dfa7918b742336277\",\n      type: \"movie\",\n      qualities: [\n        {\n          quality: \"SDTV\",\n          min: 2,\n          preferred: 95,\n          max: 100,\n        },\n      ],\n    };\n\n    const clone: TrashQualityDefinition = JSON.parse(JSON.stringify(trashQualityDef));\n\n    const resultLow = transformTrashQDs(clone, -0.5);\n    expect(resultLow[0]!.preferred).toBe(95);\n\n    const resultHigh = transformTrashQDs(clone, 1.5);\n    expect(resultHigh[0]!.preferred).toBe(95);\n  });\n\n  test(\"transformTrashCFGroups - only include required cfs\", async ({}) => {\n    const mapping: TrashCFGroupMapping = new Map();\n    mapping.set(\"id1\", {\n      name: \"name1\",\n      trash_id: \"id1\",\n      custom_formats: [\n        { name: \"cf1\", trash_id: \"cf1\", required: true },\n        { name: \"cf2\", trash_id: \"cf2\", required: false },\n      ],\n    });\n\n    const groups: InputConfigCustomFormatGroup[] = [\n      {\n        trash_guide: [{ id: \"id1\" }],\n        assign_scores_to: [{ name: \"qp1\" }],\n      },\n    ];\n\n    const result = transformTrashCFGroups(mapping, groups);\n    expect(result).toHaveLength(1);\n    expect(result[0]!.trash_ids!).toHaveLength(1);\n    expect(result[0]!.trash_ids![0]).toBe(\"cf1\");\n    expect(result[0]!.assign_scores_to).toBeDefined();\n    expect(result[0]!.assign_scores_to!).toHaveLength(1);\n    expect(result[0]!.assign_scores_to![0]?.name).toBe(\"qp1\");\n  });\n\n  test(\"transformTrashCFGroups - include all if attribute set\", async ({}) => {\n    const mapping: TrashCFGroupMapping = new Map();\n    mapping.set(\"id1\", {\n      name: \"name1\",\n      trash_id: \"id1\",\n      custom_formats: [\n        { name: \"cf1\", trash_id: \"cf1\", required: true },\n        { name: \"cf2\", trash_id: \"cf2\", required: false },\n      ],\n    });\n\n    const groups: InputConfigCustomFormatGroup[] = [\n      {\n        trash_guide: [{ id: \"id1\", include_unrequired: true }],\n        assign_scores_to: [{ name: \"qp1\" }],\n      },\n    ];\n\n    const result = transformTrashCFGroups(mapping, groups);\n\n    expect(result).toHaveLength(1);\n    expect(result[0]!.trash_ids!).toHaveLength(2);\n    expect(result[0]!.assign_scores_to).toBeDefined();\n    expect(result[0]!.assign_scores_to!).toHaveLength(1);\n    expect(result[0]!.assign_scores_to![0]?.name).toBe(\"qp1\");\n  });\n\n  test(\"transformTrashCFGroups - ignore if mapping missing\", async ({}) => {\n    const mapping: TrashCFGroupMapping = new Map();\n    mapping.set(\"id2\", {\n      name: \"name1\",\n      trash_id: \"id1\",\n      custom_formats: [\n        { name: \"cf1\", trash_id: \"cf1\", required: true },\n        { name: \"cf2\", trash_id: \"cf2\", required: false },\n      ],\n    });\n\n    const groups: InputConfigCustomFormatGroup[] = [\n      {\n        trash_guide: [{ id: \"id1\", include_unrequired: true }],\n        assign_scores_to: [{ name: \"qp1\" }],\n      },\n    ];\n\n    const result = transformTrashCFGroups(mapping, groups);\n\n    expect(result).toHaveLength(0);\n  });\n\n  test(\"transformTrashCFGroups - include score when specified\", async ({}) => {\n    const mapping: TrashCFGroupMapping = new Map();\n    mapping.set(\"id1\", {\n      name: \"name1\",\n      trash_id: \"id1\",\n      custom_formats: [\n        { name: \"cf1\", trash_id: \"cf1\", required: true },\n        { name: \"cf2\", trash_id: \"cf2\", required: false },\n      ],\n    });\n\n    const groups: InputConfigCustomFormatGroup[] = [\n      {\n        trash_guide: [{ id: \"id1\" }],\n        assign_scores_to: [{ name: \"qp1\", score: 0 }],\n      },\n    ];\n\n    const result = transformTrashCFGroups(mapping, groups);\n    expect(result).toHaveLength(1);\n    expect(result[0]!.trash_ids!).toHaveLength(1);\n    expect(result[0]!.trash_ids![0]).toBe(\"cf1\");\n    expect(result[0]!.assign_scores_to).toBeDefined();\n    expect(result[0]!.assign_scores_to![0]?.name).toBe(\"qp1\");\n    expect(result[0]!.assign_scores_to![0]?.score).toBe(0);\n  });\n\n  describe(\"transformTrashQPCFGroups - new include semantics (default)\", () => {\n    const mockTrashQP: TrashQP = {\n      trash_id: \"profile123\",\n      name: \"Test Profile\",\n      trash_score_set: \"default\",\n      upgradeAllowed: true,\n      cutoff: \"HD\",\n      minFormatScore: 0,\n      cutoffFormatScore: 100,\n      items: [],\n      formatItems: {},\n    };\n\n    test(\"should include CFs from default groups when profile is in include list\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group 1\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [\n          { name: \"cf1\", trash_id: \"cf1\", required: true },\n          { name: \"cf2\", trash_id: \"cf2\", required: false },\n        ],\n        quality_profiles: {\n          include: {\n            \"Test Profile\": \"profile123\",\n          },\n        },\n      });\n\n      // Default behavior (useExcludeSemantics = false)\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, false);\n\n      expect(result).toHaveLength(1);\n      expect(result[0]?.trash_ids).toEqual([\"cf1\"]);\n      expect(result[0]?.assign_scores_to).toEqual([{ name: \"Test Profile\" }]);\n    });\n\n    test(\"should NOT include CFs when profile is NOT in include list\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group 1\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [{ name: \"cf1\", trash_id: \"cf1\", required: true }],\n        quality_profiles: {\n          include: {\n            \"Other Profile\": \"other123\",\n          },\n        },\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, false);\n\n      expect(result).toHaveLength(0);\n    });\n\n    test(\"should NOT include CFs when include list is empty\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group 1\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [{ name: \"cf1\", trash_id: \"cf1\", required: true }],\n        quality_profiles: {\n          include: {},\n        },\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, false);\n\n      expect(result).toHaveLength(0);\n    });\n\n    test(\"should NOT include CFs when quality_profiles is undefined (no include list)\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group 1\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [{ name: \"cf1\", trash_id: \"cf1\", required: true }],\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, false);\n\n      expect(result).toHaveLength(0);\n    });\n\n    test(\"should include CFs from default groups with default=true when in include list\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group 1\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [\n          { name: \"cf1\", trash_id: \"cf1\", required: false, default: true },\n          { name: \"cf2\", trash_id: \"cf2\", required: false, default: false },\n        ],\n        quality_profiles: {\n          include: {\n            \"Test Profile\": \"profile123\",\n          },\n        },\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, false);\n\n      expect(result).toHaveLength(1);\n      expect(result[0]?.trash_ids).toEqual([\"cf1\"]);\n      expect(result[0]?.assign_scores_to).toEqual([{ name: \"Test Profile\" }]);\n    });\n\n    test(\"should include CFs with both required=true and default=true when in include list\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group 1\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [\n          { name: \"cf1\", trash_id: \"cf1\", required: true },\n          { name: \"cf2\", trash_id: \"cf2\", required: false, default: true },\n          { name: \"cf3\", trash_id: \"cf3\", required: false, default: false },\n        ],\n        quality_profiles: {\n          include: {\n            \"Test Profile\": \"profile123\",\n          },\n        },\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, false);\n\n      expect(result).toHaveLength(1);\n      expect(result[0]?.trash_ids).toEqual([\"cf1\", \"cf2\"]);\n      expect(result[0]?.assign_scores_to).toEqual([{ name: \"Test Profile\" }]);\n    });\n\n    test(\"should skip groups without default=true even if in include list\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Non-default Group\",\n        trash_id: \"group1\",\n        default: \"false\",\n        custom_formats: [{ name: \"cf1\", trash_id: \"cf1\", required: true }],\n        quality_profiles: {\n          include: {\n            \"Test Profile\": \"profile123\",\n          },\n        },\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, false);\n\n      expect(result).toHaveLength(0);\n    });\n\n    test(\"should handle multiple default groups with different include lists\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group 1\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [{ name: \"cf1\", trash_id: \"cf1\", required: true }],\n        quality_profiles: {\n          include: {\n            \"Test Profile\": \"profile123\",\n          },\n        },\n      });\n      mapping.set(\"group2\", {\n        name: \"Default Group 2\",\n        trash_id: \"group2\",\n        default: \"true\",\n        custom_formats: [{ name: \"cf2\", trash_id: \"cf2\", required: true }],\n        quality_profiles: {\n          include: {\n            \"Other Profile\": \"other123\",\n          },\n        },\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, false);\n\n      // Only group1 should be included since Test Profile is in its include list\n      expect(result).toHaveLength(1);\n      expect(result[0]?.trash_ids).toEqual([\"cf1\"]);\n    });\n\n    test(\"should return empty array when no CFs match criteria\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group with no matching CFs\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [{ name: \"cf1\", trash_id: \"cf1\", required: false, default: false }],\n        quality_profiles: {\n          include: {\n            \"Test Profile\": \"profile123\",\n          },\n        },\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, false);\n\n      expect(result).toHaveLength(0);\n    });\n\n    test(\"should handle empty mapping\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, false);\n\n      expect(result).toHaveLength(0);\n    });\n\n    test(\"default behavior (no third param) should use include semantics\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group 1\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [{ name: \"cf1\", trash_id: \"cf1\", required: true }],\n        // No include list - should NOT include\n      });\n\n      // Not passing third param - should default to false (include semantics)\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping);\n\n      expect(result).toHaveLength(0);\n    });\n  });\n\n  describe(\"transformTrashQPCFGroups - legacy exclude semantics (compatibility mode)\", () => {\n    const mockTrashQP: TrashQP = {\n      trash_id: \"profile123\",\n      name: \"Test Profile\",\n      trash_score_set: \"default\",\n      upgradeAllowed: true,\n      cutoff: \"HD\",\n      minFormatScore: 0,\n      cutoffFormatScore: 100,\n      items: [],\n      formatItems: {},\n    };\n\n    test(\"should include CFs from default groups with required=true (legacy mode)\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group 1\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [\n          { name: \"cf1\", trash_id: \"cf1\", required: true },\n          { name: \"cf2\", trash_id: \"cf2\", required: false },\n        ],\n      });\n\n      // Legacy mode (useExcludeSemantics = true)\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, true);\n\n      expect(result).toHaveLength(1);\n      expect(result[0]?.trash_ids).toEqual([\"cf1\"]);\n      expect(result[0]?.assign_scores_to).toEqual([{ name: \"Test Profile\" }]);\n    });\n\n    test(\"should include CFs from default groups with default=true (legacy mode)\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group 1\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [\n          { name: \"cf1\", trash_id: \"cf1\", required: false, default: true },\n          { name: \"cf2\", trash_id: \"cf2\", required: false, default: false },\n        ],\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, true);\n\n      expect(result).toHaveLength(1);\n      expect(result[0]?.trash_ids).toEqual([\"cf1\"]);\n      expect(result[0]?.assign_scores_to).toEqual([{ name: \"Test Profile\" }]);\n    });\n\n    test(\"should include CFs with both required=true and default=true (legacy mode)\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group 1\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [\n          { name: \"cf1\", trash_id: \"cf1\", required: true },\n          { name: \"cf2\", trash_id: \"cf2\", required: false, default: true },\n          { name: \"cf3\", trash_id: \"cf3\", required: false, default: false },\n        ],\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, true);\n\n      expect(result).toHaveLength(1);\n      expect(result[0]?.trash_ids).toEqual([\"cf1\", \"cf2\"]);\n      expect(result[0]?.assign_scores_to).toEqual([{ name: \"Test Profile\" }]);\n    });\n\n    test(\"should skip groups without default=true (legacy mode)\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Non-default Group\",\n        trash_id: \"group1\",\n        default: \"false\",\n        custom_formats: [{ name: \"cf1\", trash_id: \"cf1\", required: true }],\n      });\n      mapping.set(\"group2\", {\n        name: \"No Default Group\",\n        trash_id: \"group2\",\n        custom_formats: [{ name: \"cf2\", trash_id: \"cf2\", required: true }],\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, true);\n\n      expect(result).toHaveLength(0);\n    });\n\n    test(\"should respect exclude field for profile (legacy mode)\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Excluded Group\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [{ name: \"cf1\", trash_id: \"cf1\", required: true }],\n        quality_profiles: {\n          exclude: {\n            \"Test Profile\": \"profile123\",\n          },\n        },\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, true);\n\n      expect(result).toHaveLength(0);\n    });\n\n    test(\"should include from non-excluded profiles only (legacy mode)\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Partially Excluded Group\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [{ name: \"cf1\", trash_id: \"cf1\", required: true }],\n        quality_profiles: {\n          exclude: {\n            \"Other Profile\": \"other123\",\n          },\n        },\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, true);\n\n      expect(result).toHaveLength(1);\n      expect(result[0]?.trash_ids).toEqual([\"cf1\"]);\n    });\n\n    test(\"should handle multiple default groups (legacy mode)\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group 1\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [{ name: \"cf1\", trash_id: \"cf1\", required: true }],\n      });\n      mapping.set(\"group2\", {\n        name: \"Default Group 2\",\n        trash_id: \"group2\",\n        default: \"true\",\n        custom_formats: [{ name: \"cf2\", trash_id: \"cf2\", required: true }],\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, true);\n\n      expect(result).toHaveLength(2);\n      expect(result[0]?.trash_ids).toEqual([\"cf1\"]);\n      expect(result[1]?.trash_ids).toEqual([\"cf2\"]);\n      expect(result[0]?.assign_scores_to).toEqual([{ name: \"Test Profile\" }]);\n      expect(result[1]?.assign_scores_to).toEqual([{ name: \"Test Profile\" }]);\n    });\n\n    test(\"should return empty array when no CFs match criteria (legacy mode)\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n      mapping.set(\"group1\", {\n        name: \"Default Group with no matching CFs\",\n        trash_id: \"group1\",\n        default: \"true\",\n        custom_formats: [{ name: \"cf1\", trash_id: \"cf1\", required: false, default: false }],\n      });\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, true);\n\n      expect(result).toHaveLength(0);\n    });\n\n    test(\"should handle empty mapping (legacy mode)\", () => {\n      const mapping: TrashCFGroupMapping = new Map();\n\n      const result = transformTrashQPCFGroups(mockTrashQP, mapping, true);\n\n      expect(result).toHaveLength(0);\n    });\n  });\n\n  describe(\"loadTrashCFConflicts\", () => {\n    test(\"should return empty array for unsupported arrType\", async () => {\n      const result = await loadTrashCFConflicts(\"LIDARR\" as any);\n\n      expect(result).toEqual([]);\n    });\n\n    test(\"should return empty array and warn when file not found\", async () => {\n      const error = new Error(\"ENOENT: no such file\");\n      (error as any).code = \"ENOENT\";\n      vi.spyOn(util, \"loadJsonFile\").mockImplementation(() => {\n        throw error;\n      });\n\n      const result = await loadTrashCFConflicts(\"RADARR\");\n\n      expect(result).toEqual([]);\n    });\n\n    test(\"should return empty array and warn when file is invalid\", async () => {\n      vi.spyOn(util, \"loadJsonFile\").mockReturnValue({ invalid: \"format\" });\n\n      const result = await loadTrashCFConflicts(\"RADARR\");\n\n      expect(result).toEqual([]);\n    });\n\n    test(\"should skip conflict groups with fewer than 2 valid trash_ids\", async () => {\n      const mockConflicts = {\n        custom_formats: [\n          {\n            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: { name: \"Only one\", desc: \"\" },\n          },\n          {\n            bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: { name: \"CF2\", desc: \"\" },\n            cccccccccccccccccccccccccccccccc: { name: \"CF3\", desc: \"\" },\n          },\n        ],\n      };\n\n      vi.spyOn(util, \"loadJsonFile\").mockReturnValue(mockConflicts);\n\n      const result = await loadTrashCFConflicts(\"RADARR\");\n\n      expect(result).toHaveLength(1);\n      expect(result[0]?.trash_id).toBe(\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb+cccccccccccccccccccccccccccccccc\");\n      expect(result[0]?.custom_formats).toHaveLength(2);\n    });\n\n    test(\"should skip null and empty groups but load valid ones\", async () => {\n      const mockConflicts = {\n        custom_formats: [\n          {\n            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: { name: \"CF1\", desc: \"\" },\n            bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: { name: \"CF2\", desc: \"\" },\n          },\n          null,\n          {},\n          {\n            cccccccccccccccccccccccccccccccc: { name: \"CF3\", desc: \"\" },\n            dddddddddddddddddddddddddddddddd: { name: \"CF4\", desc: \"\" },\n          },\n        ],\n      };\n\n      vi.spyOn(util, \"loadJsonFile\").mockReturnValue(mockConflicts);\n\n      const result = await loadTrashCFConflicts(\"RADARR\");\n\n      expect(result).toHaveLength(2);\n      expect(result[0]?.trash_id).toBe(\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\");\n      expect(result[1]?.trash_id).toBe(\"cccccccccccccccccccccccccccccccc+dddddddddddddddddddddddddddddddd\");\n    });\n\n    test(\"should normalize one group (desc join and trash_id sort order)\", async () => {\n      const mockConflicts = {\n        custom_formats: [\n          {\n            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: { name: \"First alpha id\", desc: \"alpha\" },\n            bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: { name: \"Second name\", desc: \"beta\" },\n          },\n        ],\n      };\n\n      vi.spyOn(util, \"loadJsonFile\").mockReturnValue(mockConflicts);\n\n      const result = await loadTrashCFConflicts(\"RADARR\");\n\n      expect(result).toHaveLength(1);\n      expect(result[0]?.trash_id).toBe(\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\");\n      expect(result[0]?.name).toBe(\"First alpha id vs Second name\");\n      expect(result[0]?.trash_description).toBe(\"alpha beta\");\n      expect(result[0]?.custom_formats).toEqual([\n        { trash_id: \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", name: \"First alpha id\" },\n        { trash_id: \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\", name: \"Second name\" },\n      ]);\n    });\n\n    test(\"should load conflicts.json matching TRaSH upstream sample\", async () => {\n      const mockConflicts = {\n        $schema: \"../../../schemas/conflicts.schema.json\",\n        custom_formats: [\n          {\n            \"9c38ebb7384dada637be8899efa68e6f\": { name: \"SDR\", desc: \"\" },\n            \"25c12f78430a3a23413652cbd1d48d77\": { name: \"SDR (no WEBDL)\", desc: \"\" },\n          },\n          {\n            dc98083864ea246d05a42df0d05f81cc: { name: \"x265 (HD)\", desc: \"a\" },\n            \"839bea857ed2c0a8e084f3cbdbd65ecb\": { name: \"x265 (no HDR/DV)\", desc: \"b\" },\n          },\n        ],\n      };\n\n      vi.spyOn(util, \"loadJsonFile\").mockReturnValue(mockConflicts);\n\n      const result = await loadTrashCFConflicts(\"RADARR\");\n\n      expect(result).toHaveLength(2);\n      expect(result[0]?.trash_id).toBe(\"25c12f78430a3a23413652cbd1d48d77+9c38ebb7384dada637be8899efa68e6f\");\n      expect(result[0]?.name).toBe(\"SDR (no WEBDL) vs SDR\");\n      expect(result[0]?.custom_formats).toEqual([\n        { trash_id: \"25c12f78430a3a23413652cbd1d48d77\", name: \"SDR (no WEBDL)\" },\n        { trash_id: \"9c38ebb7384dada637be8899efa68e6f\", name: \"SDR\" },\n      ]);\n      expect(result[1]?.trash_id).toBe(\"839bea857ed2c0a8e084f3cbdbd65ecb+dc98083864ea246d05a42df0d05f81cc\");\n      expect(result[1]?.name).toBe(\"x265 (no HDR/DV) vs x265 (HD)\");\n      expect(result[1]?.trash_description).toBe(\"b a\");\n    });\n\n    test(\"should return cached conflicts when cache is ready\", async () => {\n      const mockConflicts = {\n        custom_formats: [\n          {\n            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: { name: \"CF1\", desc: \"\" },\n            bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: { name: \"CF2\", desc: \"\" },\n          },\n        ],\n      };\n\n      const loadJsonSpy = vi.spyOn(util, \"loadJsonFile\").mockReturnValue(mockConflicts);\n\n      // First call should load from file\n      const result1 = await loadTrashCFConflicts(\"RADARR\");\n      expect(result1).toHaveLength(1);\n      expect(loadJsonSpy).toHaveBeenCalledTimes(1);\n\n      // Second call should use cache (loadJsonFile not called again)\n      // Note: In this test setup, we can't actually set cacheReady = true,\n      // but this test ensures the function handles cache correctly when used\n    });\n  });\n});\n"
  },
  {
    "path": "src/trash-guide.ts",
    "content": "import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\nimport { MergedCustomFormatResource } from \"./types/merged.types\";\nimport { getConfig } from \"./config\";\nimport { logger } from \"./logger\";\nimport { interpolateSize } from \"./quality-definitions\";\nimport { CFIDToConfigGroup, ConfigarrCF, QualityDefinitionsRadarr, QualityDefinitionsSonarr } from \"./types/common.types\";\nimport { ConfigCustomFormat, ConfigQualityProfile, ConfigQualityProfileItem, InputConfigCustomFormatGroup } from \"./types/config.types\";\nimport {\n  TrashArrSupported,\n  TrashCache,\n  TrashCF,\n  TrashCFConflict,\n  TrashCFGroupMapping,\n  TrashCustomFormatGroups,\n  TrashQP,\n  TrashQualityDefinition,\n  TrashQualityDefinitionQuality,\n  TrashRadarrNaming,\n  TrashSonarrNaming,\n} from \"./types/trashguide.types\";\nimport { cloneGitRepo, loadJsonFile, mapImportCfToRequestCf, notEmpty, toCarrCF, trashRepoPaths } from \"./util\";\n\nconst DEFAULT_TRASH_GIT_URL = \"https://github.com/TRaSH-Guides/Guides\";\n\nlet cache: TrashCache;\nlet cacheReady = false;\n\nconst createCache = async () => {\n  logger.debug(`Creating TRaSH-Guides cache ...`);\n\n  const radarrCF = await loadTrashCFs(\"RADARR\");\n  const sonarrCF = await loadTrashCFs(\"SONARR\");\n\n  const radarrCFGroups = await loadTrashCustomFormatGroups(\"RADARR\");\n  const sonarrCFGroups = await loadTrashCustomFormatGroups(\"SONARR\");\n\n  const radarrNaming = await loadNamingFromTrashRadarr();\n  const sonarrNaming = await loadNamingFromTrashSonarr();\n\n  const radarrQP = await loadQPFromTrash(\"RADARR\");\n  const sonarrQP = await loadQPFromTrash(\"SONARR\");\n\n  const radarrQDMovie = await loadQualityDefinitionFromTrash(\"movie\", \"RADARR\");\n  const sonarrQDSeries = await loadQualityDefinitionFromTrash(\"series\", \"SONARR\");\n  const sonarrQDAnime = await loadQualityDefinitionFromTrash(\"anime\", \"SONARR\");\n\n  const radarrConflicts = await loadTrashCFConflicts(\"RADARR\");\n  const sonarrConflicts = await loadTrashCFConflicts(\"SONARR\");\n\n  cache = {\n    SONARR: {\n      qualityProfiles: sonarrQP,\n      customFormats: sonarrCF,\n      customFormatsGroups: sonarrCFGroups,\n      qualityDefinition: {\n        anime: sonarrQDAnime,\n        series: sonarrQDSeries,\n      },\n      naming: sonarrNaming,\n      conflicts: sonarrConflicts,\n    },\n    RADARR: {\n      qualityProfiles: radarrQP,\n      customFormats: radarrCF,\n      customFormatsGroups: radarrCFGroups,\n      qualityDefinition: {\n        movie: radarrQDMovie,\n      },\n      naming: radarrNaming,\n      conflicts: radarrConflicts,\n    },\n  };\n\n  cacheReady = true;\n};\n\nexport const cloneTrashRepo = async () => {\n  logger.info(`Checking TRaSH-Guides repo ...`);\n\n  const rootPath = trashRepoPaths.root;\n  const applicationConfig = getConfig();\n  const gitUrl = applicationConfig.trashGuideUrl ?? DEFAULT_TRASH_GIT_URL;\n  const revision = applicationConfig.trashRevision ?? \"master\";\n  const sparseDisabled = applicationConfig.enableFullGitClone === true;\n\n  const cloneResult = await cloneGitRepo(rootPath, gitUrl, revision, {\n    disabled: sparseDisabled,\n    sparseDirs: [\"docs/json\"],\n  });\n  logger.info(`TRaSH-Guides repo: ref[${cloneResult.ref}], hash[${cloneResult.hash}], path[${cloneResult.localPath}]`);\n  await createCache();\n};\n\nexport const loadTrashCFs = async (arrType: TrashArrSupported): Promise<CFIDToConfigGroup> => {\n  if (arrType !== \"RADARR\" && arrType !== \"SONARR\") {\n    logger.debug(`Unsupported arrType: ${arrType}. Skipping TrashCFs.`);\n\n    return new Map();\n  }\n\n  if (cacheReady) {\n    return cache[arrType].customFormats;\n  }\n\n  const carrIdToObject = new Map<string, { carrConfig: ConfigarrCF; requestConfig: MergedCustomFormatResource }>();\n\n  let pathForFiles: string;\n\n  if (arrType === \"RADARR\") {\n    pathForFiles = trashRepoPaths.radarrCF;\n  } else {\n    pathForFiles = trashRepoPaths.sonarrCF;\n  }\n\n  const files = fs.readdirSync(pathForFiles).filter((fn) => fn.endsWith(\"json\"));\n\n  for (const file of files) {\n    const name = `${pathForFiles}/${file}`;\n\n    const cf = loadJsonFile<TrashCF>(path.resolve(name));\n\n    const carrConfig = toCarrCF(cf);\n\n    carrIdToObject.set(carrConfig.configarr_id, {\n      carrConfig: carrConfig,\n      requestConfig: mapImportCfToRequestCf(carrConfig),\n    });\n  }\n\n  logger.debug(`(${arrType}) Trash CFs: ${carrIdToObject.size}`);\n\n  return carrIdToObject;\n};\n\nexport const loadTrashCustomFormatGroups = async (arrType: TrashArrSupported): Promise<TrashCFGroupMapping> => {\n  if (arrType !== \"RADARR\" && arrType !== \"SONARR\") {\n    logger.debug(`Unsupported arrType: ${arrType}. Skipping TrashCustomFormatGroups.`);\n\n    return new Map();\n  }\n\n  if (cacheReady) {\n    return cache[arrType].customFormatsGroups;\n  }\n\n  const cfGroupMapping: TrashCFGroupMapping = new Map();\n\n  let pathForFiles: string;\n\n  if (arrType === \"RADARR\") {\n    pathForFiles = trashRepoPaths.radarrCFGroups;\n  } else {\n    pathForFiles = trashRepoPaths.sonarrCFGroups;\n  }\n\n  const files = fs.readdirSync(pathForFiles).filter((fn) => fn.endsWith(\"json\"));\n\n  for (const file of files) {\n    const name = `${pathForFiles}/${file}`;\n\n    const cfGroup = loadJsonFile<TrashCustomFormatGroups>(path.resolve(name));\n\n    cfGroupMapping.set(cfGroup.trash_id, cfGroup);\n  }\n\n  logger.debug(`(${arrType}) Trash CustomFormatGroups: ${cfGroupMapping.size}`);\n\n  return cfGroupMapping;\n};\n\nexport const loadQualityDefinitionFromTrash = async (\n  qdType: string, // QualityDefinitionsSonarr | QualityDefinitionsRadarr,\n  arrType: TrashArrSupported,\n): Promise<TrashQualityDefinition> => {\n  let trashPath = arrType === \"RADARR\" ? trashRepoPaths.radarrQualitySize : trashRepoPaths.sonarrQualitySize;\n\n  if (cacheReady) {\n    const cacheObject = cache[arrType].qualityDefinition as any;\n    if (qdType in cacheObject) {\n      return cacheObject[qdType];\n    }\n  }\n\n  // TODO: custom quality definition not implemented yet. Not sure if we need to implement this. Qualities can already be defined separately.\n  const filePath = path.resolve(`${trashPath}/${qdType}.json`);\n\n  if (!fs.existsSync(filePath)) {\n    throw new Error(`(${arrType}) QualityDefinition type not found: '${qdType}' for '${arrType}'`);\n  }\n\n  return loadJsonFile(filePath);\n};\n\nexport const loadAllQDsFromTrash = async (arrType: TrashArrSupported): Promise<Map<string, TrashQualityDefinition>> => {\n  const trashPath = arrType === \"RADARR\" ? trashRepoPaths.radarrQualitySize : trashRepoPaths.sonarrQualitySize;\n  const map = new Map<string, TrashQualityDefinition>();\n\n  // Note: intentionally bypasses module-level cache — the pre-warmed cache only contains\n  // a subset of QD types (movie, series, anime). This function must load ALL files from\n  // the quality-size directory, including types not pre-cached (e.g. sqp-streaming, sqp-uhd).\n  try {\n    const files = fs.readdirSync(trashPath).filter((fn) => fn.endsWith(\".json\"));\n    for (const item of files) {\n      try {\n        const qd = loadJsonFile<TrashQualityDefinition>(`${trashPath}/${item}`);\n        map.set(qd.trash_id, qd);\n      } catch (err: unknown) {\n        const message = err instanceof Error ? err.message : String(err);\n        logger.warn(`(${arrType}) Failed loading TRaSH-Guides QualityDefinition from '${item}'. Skipping. ${message}`);\n      }\n    }\n  } catch (err: unknown) {\n    const message = err instanceof Error ? err.message : String(err);\n    logger.warn(`(${arrType}) Failed loading TRaSH-Guides QualityDefinitions from quality-size. Continue without ... ${message}`);\n  }\n\n  return map;\n};\n\nexport const loadQPFromTrash = async (arrType: TrashArrSupported) => {\n  let trashPath = arrType === \"RADARR\" ? trashRepoPaths.radarrQP : trashRepoPaths.sonarrQP;\n\n  if (cacheReady) {\n    return cache[arrType].qualityProfiles;\n  }\n\n  const map = new Map<string, TrashQP>();\n\n  try {\n    const files = fs.readdirSync(`${trashPath}`).filter((fn) => fn.endsWith(\"json\"));\n\n    if (files.length <= 0) {\n      logger.info(`(${arrType}) Not found any TRaSH-Guides QualityProfiles. Skipping.`);\n    }\n\n    for (const item of files) {\n      const importTrashQP = loadJsonFile<TrashQP>(`${trashPath}/${item}`);\n\n      map.set(importTrashQP.trash_id, importTrashQP);\n    }\n  } catch (err: any) {\n    logger.warn(`(${arrType}) Failed loading TRaSH-Guides QualityProfiles. Continue without ...`, err?.message);\n  }\n\n  // const localPath = getLocalTemplatePath();\n\n  // if (localPath) {\n  //   fillMap(localPath);\n  // }\n\n  logger.debug(`(${arrType}) Found ${map.size} TRaSH-Guides QualityProfiles.`);\n  return map;\n};\n\n/**\n * HINT: for now we only support one naming file per arrType\n */\nconst loadNamingFromTrash = async <T>(arrType: TrashArrSupported): Promise<T | null> => {\n  let trashPath = arrType === \"RADARR\" ? trashRepoPaths.radarrNaming : trashRepoPaths.sonarrNaming;\n\n  const map = new Map<string, T>();\n\n  try {\n    const files = fs.readdirSync(`${trashPath}`).filter((fn) => fn.endsWith(\"json\"));\n\n    if (files.length <= 0) {\n      logger.info(`(${arrType}) Not found any TRaSH-Guides Naming files. Skipping.`);\n    }\n\n    for (const item of files) {\n      const importTrashQP = loadJsonFile<T>(`${trashPath}/${item}`);\n\n      map.set(item, importTrashQP);\n    }\n  } catch (err: any) {\n    logger.warn(`(${arrType}) Failed loading TRaSH-Guides QualityProfiles. Continue without ...`, err?.message);\n  }\n\n  logger.debug(`(${arrType}) Found ${map.size} TRaSH-Guides Naming files.`);\n\n  if (map.size <= 0) {\n    return null;\n  }\n\n  if (map.size > 1) {\n    logger.warn(`(${arrType}) Found more than one TRaSH-Guides Naming file. Using the first one.`);\n  }\n\n  const firstValue = map.values().next().value!;\n\n  return firstValue;\n};\n\nexport const loadNamingFromTrashSonarr = async (): Promise<TrashSonarrNaming | null> => {\n  if (cacheReady) {\n    return cache[\"SONARR\"].naming;\n  }\n\n  const firstValue = await loadNamingFromTrash<TrashSonarrNaming>(\"SONARR\");\n\n  if (firstValue == null) {\n    return firstValue;\n  }\n\n  logger.debug(`(SONARR) Available TRaSH-Guide season keys: ${Object.keys(firstValue.season)}`);\n  logger.debug(`(SONARR) Available TRaSH-Guide series keys: ${Object.keys(firstValue.series)}`);\n  logger.debug(`(SONARR) Available TRaSH-Guide standard episode keys: ${Object.keys(firstValue.episodes.standard)}`);\n  logger.debug(`(SONARR) Available TRaSH-Guide daily episode keys: ${Object.keys(firstValue.episodes.daily)}`);\n  logger.debug(`(SONARR) Available TRaSH-Guide anime episode keys: ${Object.keys(firstValue.episodes.anime)}`);\n\n  return firstValue;\n};\n\nexport const loadNamingFromTrashRadarr = async (): Promise<TrashRadarrNaming | null> => {\n  if (cacheReady) {\n    return cache[\"RADARR\"].naming;\n  }\n\n  const firstValue = await loadNamingFromTrash<TrashRadarrNaming>(\"RADARR\");\n\n  if (firstValue == null) {\n    return firstValue;\n  }\n\n  logger.debug(`(RADARR) Available TRaSH-Guide folder keys: ${Object.keys(firstValue.folder)}`);\n  logger.debug(`(RADARR) Available TRaSH-Guide file keys: ${Object.keys(firstValue.file)}`);\n\n  return firstValue;\n};\n\n/** Matches TRaSH conflicts.schema.json (object values: name required, desc optional, no extra keys). */\nconst trashConflictCfEntrySchema = z\n  .object({\n    name: z.string(),\n    desc: z.string().optional(),\n  })\n  .strict();\n\nconst trashConflictGroupRecordSchema = z.record(z.string(), trashConflictCfEntrySchema);\n\nconst trashConflictsFileSchema = z\n  .object({\n    $schema: z.string().optional(),\n    custom_formats: z.array(z.unknown()),\n  })\n  .strict();\n\nconst normalizeTrashConflictGroup = (group: z.infer<typeof trashConflictGroupRecordSchema>): TrashCFConflict => {\n  const entries = Object.entries(group).map(([trash_id, rec]) => ({\n    trash_id,\n    name: rec.name,\n    desc: rec.desc,\n  }));\n  entries.sort((a, b) => a.trash_id.localeCompare(b.trash_id));\n  const custom_formats = entries.map(({ trash_id, name }) => ({ trash_id, name }));\n  const descriptions = entries.map((e) => e.desc).filter((d): d is string => typeof d === \"string\" && d.length > 0);\n\n  return {\n    trash_id: custom_formats.map((cf) => cf.trash_id).join(\"+\"),\n    name: custom_formats.map((cf) => cf.name).join(\" vs \"),\n    trash_description: descriptions.length > 0 ? descriptions.join(\" \") : undefined,\n    custom_formats,\n  };\n};\n\nexport const loadTrashCFConflicts = async (arrType: TrashArrSupported): Promise<TrashCFConflict[]> => {\n  if (arrType !== \"RADARR\" && arrType !== \"SONARR\") {\n    logger.debug(`Unsupported arrType: ${arrType}. Skipping TrashCFConflicts.`);\n    return [];\n  }\n\n  if (cacheReady) {\n    return cache[arrType].conflicts;\n  }\n\n  const conflicts: TrashCFConflict[] = [];\n\n  let conflictsPath: string;\n\n  if (arrType === \"RADARR\") {\n    conflictsPath = trashRepoPaths.radarrConflicts;\n  } else {\n    conflictsPath = trashRepoPaths.sonarrConflicts;\n  }\n\n  try {\n    const raw = loadJsonFile<unknown>(conflictsPath);\n    const fileParsed = trashConflictsFileSchema.safeParse(raw);\n\n    if (!fileParsed.success) {\n      logger.warn(`(${arrType}) Invalid conflicts.json: ${fileParsed.error.issues.map((i) => i.message).join(\"; \")}. Skipping conflicts.`);\n      return [];\n    }\n\n    for (const group of fileParsed.data.custom_formats) {\n      if (group === null || typeof group !== \"object\" || Array.isArray(group)) {\n        continue;\n      }\n\n      const g = trashConflictGroupRecordSchema.safeParse(group);\n      if (!g.success) {\n        logger.warn(`(${arrType}) Skipping invalid conflict group: ${g.error.issues.map((i) => i.message).join(\"; \")}`);\n        continue;\n      }\n\n      const keyCount = Object.keys(g.data).length;\n      if (keyCount < 2) {\n        if (keyCount === 0) {\n          logger.debug(`(${arrType}) Skipping empty conflict group`);\n        } else {\n          logger.warn(`(${arrType}) Skipping invalid conflict group: need at least 2 entries (string key -> { name, desc? })`);\n        }\n        continue;\n      }\n\n      conflicts.push(normalizeTrashConflictGroup(g.data));\n    }\n\n    logger.debug(`(${arrType}) Loaded ${conflicts.length} TRaSH CF conflict groups`);\n    return conflicts;\n  } catch (err: unknown) {\n    if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n      logger.debug(`(${arrType}) conflicts.json not found. Skipping conflicts.`);\n    } else {\n      const message = err instanceof Error ? err.message : String(err);\n      logger.warn(`(${arrType}) Failed loading conflicts.json. Skipping conflicts: ${message}`);\n    }\n    return [];\n  }\n};\n\n// TODO merge two methods?\nexport const transformTrashQPToTemplate = (data: TrashQP, useOldQualityOrder: boolean = false): ConfigQualityProfile => {\n  const items = data.items\n    .map((e): ConfigQualityProfileItem | null => {\n      if (!e.allowed) {\n        return null;\n      }\n      // TRaSH-Guides now provides qualities in display order (highest-to-lowest)\n      // Old behavior (useOldQualityOrder=true): reverse nested items to convert from API order\n      // New behavior (useOldQualityOrder=false): use items as-is since they're already in display order\n      // Use a non-mutating reverse to avoid side effects on the original TrashQP data\n      return {\n        name: e.name,\n        qualities: useOldQualityOrder ? (e.items ? [...e.items].reverse() : undefined) : e.items,\n      };\n    })\n    .filter(notEmpty);\n\n  // Old behavior: reverse the entire array to convert from API order to display order\n  // New behavior: use items as-is since TRaSH-Guides now provides them in display order\n  const qualities = useOldQualityOrder ? items.toReversed() : items;\n\n  return {\n    min_format_score: data.minFormatScore,\n    score_set: data.trash_score_set,\n    upgrade: { allowed: data.upgradeAllowed, until_quality: data.cutoff, until_score: data.cutoffFormatScore },\n    name: data.name,\n    qualities,\n    quality_sort: \"top\", // default\n    language: data.language,\n  };\n};\n\nexport const transformTrashQPCFs = (data: TrashQP): ConfigCustomFormat => {\n  return { assign_scores_to: [{ name: data.name }], trash_ids: Object.values(data.formatItems) };\n};\n\nexport const transformTrashQPCFGroups = (\n  template: TrashQP,\n  trashCFGroupMapping: TrashCFGroupMapping,\n  useExcludeSemantics: boolean = false,\n): ConfigCustomFormat[] => {\n  const profileName = template.name;\n  const results: ConfigCustomFormat[] = [];\n\n  // Traverse each CF group and check for default=true\n  for (const [_, cfGroup] of trashCFGroupMapping) {\n    // Check if the default prop is truthy (string \"true\")\n    if (cfGroup.default === \"true\") {\n      if (useExcludeSemantics) {\n        // OLD BEHAVIOR: Check if template is excluded via exclude field\n        const isExcluded = cfGroup.quality_profiles?.exclude?.[profileName] != null;\n\n        if (!isExcluded) {\n          // Include all required and default CFs from this group\n          const cfsToInclude = cfGroup.custom_formats.filter((cf) => cf.required || cf.default === true);\n\n          if (cfsToInclude.length > 0) {\n            logger.debug(\n              `Including ${cfsToInclude.length} [${cfsToInclude.map((cf) => cf.name).join(\", \")}] CFs from default group '${cfGroup.name}' for TrashGuide profile '${profileName}'`,\n            );\n\n            results.push({\n              trash_ids: cfsToInclude.map((cf) => cf.trash_id),\n              assign_scores_to: [{ name: profileName }],\n            });\n          }\n        } else {\n          logger.debug(`Excluding default CF group '${cfGroup.name}' for TrashGuide profile '${profileName}' due to exclude field`);\n        }\n      } else {\n        // NEW BEHAVIOR: Check if template is included via include field\n        const isIncluded = cfGroup.quality_profiles?.include?.[profileName] != null;\n\n        if (isIncluded) {\n          // Include all required and default CFs from this group\n          const cfsToInclude = cfGroup.custom_formats.filter((cf) => cf.required || cf.default === true);\n\n          if (cfsToInclude.length > 0) {\n            logger.debug(\n              `Including ${cfsToInclude.length} [${cfsToInclude.map((cf) => cf.name).join(\", \")}] CFs from default group '${cfGroup.name}' for TrashGuide profile '${profileName}'`,\n            );\n\n            results.push({\n              trash_ids: cfsToInclude.map((cf) => cf.trash_id),\n              assign_scores_to: [{ name: profileName }],\n            });\n          }\n        } else {\n          const hasIncludeList = cfGroup.quality_profiles?.include != null;\n          const reason = hasIncludeList ? \"(profile not in include list)\" : \"(no include list defined for this group)\";\n          logger.debug(`Skipping default CF group '${cfGroup.name}' for TrashGuide profile '${profileName}' ${reason}`);\n        }\n      }\n    }\n  }\n\n  return results;\n};\n\nexport const transformTrashQDs = (data: TrashQualityDefinition, ratio: number | undefined): TrashQualityDefinitionQuality[] => {\n  if (ratio == null || ratio < 0 || ratio > 1) {\n    return data.qualities;\n  }\n\n  // TODO: maybe add check for duplicates?\n  const transformQualities = data.qualities.map((trashQuality) => {\n    // Adjust preffered size if preferedRatio is set\n    const adjustedPreferred = interpolateSize(trashQuality.min, trashQuality.max, trashQuality.preferred, ratio);\n    logger.debug(`QualityDefinition \"${trashQuality.quality} adjusting preferred by ratio ${ratio} to value \"${adjustedPreferred}\"`);\n\n    return { ...trashQuality, preferred: adjustedPreferred };\n  });\n\n  return transformQualities;\n};\n\nexport const transformTrashCFGroups = (trashCFGroupMapping: TrashCFGroupMapping, groups: InputConfigCustomFormatGroup[]) => {\n  return groups.reduce<ConfigCustomFormat[]>((p, c) => {\n    c.trash_guide?.forEach(({ id: trashId, include_unrequired }) => {\n      const mapping = trashCFGroupMapping.get(trashId);\n\n      if (mapping == null) {\n        logger.warn(`Trash CustomFormat Group: ${trashId} is unknown.`);\n      } else {\n        const groupCfs = mapping.custom_formats.filter((e) => e.required || include_unrequired === true).map((e) => e.trash_id);\n\n        const customFormatEntry = {\n          trash_ids: groupCfs,\n          assign_scores_to: c.assign_scores_to?.map((v) => ({ name: v.name, score: v.score })) || [],\n        };\n\n        p.push(customFormatEntry);\n      }\n    });\n    return p;\n  }, []);\n};\n"
  },
  {
    "path": "src/types/arr.types.ts",
    "content": ""
  },
  {
    "path": "src/types/common.types.ts",
    "content": "import { MergedCustomFormatResource, MergedCustomFormatSpecificationSchema } from \"./merged.types\";\nimport { InputConfigArrInstance } from \"./config.types\";\nimport { TrashCF, TrashCFSpF } from \"./trashguide.types\";\n\nexport type DynamicImportType<T> = { default: T };\n\ntype RequireAtLeastOne<T> = {\n  [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>;\n}[keyof T];\n\n/** Used in the UI of Sonarr/Radarr to import. Trash JSON are based on that so users can copy&paste stuff */\nexport type UserFriendlyField = {\n  name?: string | null; // TODO validate if this can really appear? As Input\n  value?: any;\n} & Pick<MergedCustomFormatSpecificationSchema, \"negate\" | \"required\">;\n\n/*\nLanguage values:\n0 = Unknown\n-2 = Original\n*/\nexport type CustomFormatImportImplementation =\n  | \"ReleaseTitleSpecification\" // Value string\n  | \"LanguageSpecification\" // value number\n  | \"SizeSpecification\" // special\n  | \"IndexerFlagSpecification\" // value number\n  | \"SourceSpecification\" // value number\n  | \"ResolutionSpecification\" // value number\n  | \"ReleaseGroupSpecification\"; // value string\n\nexport type TC1 = OmitTyped<MergedCustomFormatSpecificationSchema, \"fields\"> & {\n  implementation: \"ReleaseTitleSpecification\" | \"LanguageSpecification\";\n  fields?: RequireAtLeastOne<TrashCFSpF> | null;\n};\n\nexport type TC2 = OmitTyped<MergedCustomFormatSpecificationSchema, \"fields\"> & {\n  implementation: \"SizeSpecification\";\n  fields?: RequireAtLeastOne<TrashCFSpF>;\n};\n\nexport type TCM = TC1 | TC2;\n\nexport type ImportCF = OmitTyped<MergedCustomFormatResource, \"specifications\"> & {\n  specifications?: TCM[] | null;\n} & Required<Pick<MergedCustomFormatResource, \"name\">>;\n\nexport type ConfigarrCFMeta = {\n  configarr_id: string;\n  configarr_scores?: TrashCF[\"trash_scores\"];\n};\n\nexport type ConfigarrCF = ConfigarrCFMeta & ImportCF;\n\ntype CFConfigGroup = {\n  carrConfig: ConfigarrCF;\n  requestConfig: MergedCustomFormatResource;\n};\n\nexport type CFIDToConfigGroup = Map<string, CFConfigGroup>;\n\nexport type CFProcessing = {\n  carrIdMapping: CFIDToConfigGroup;\n  /** Last merge-order winner per CF `name` (same row Sonarr/Radarr); used by manageCf. */\n  cfNameToCarrConfig: Map<string, ConfigarrCF>;\n};\n\nexport type MappedTemplates = Partial<\n  Pick<\n    InputConfigArrInstance,\n    | \"quality_definition\"\n    | \"custom_formats\"\n    | \"custom_format_groups\"\n    | \"include\"\n    | \"quality_profiles\"\n    | \"customFormatDefinitions\"\n    | \"media_management\"\n    | \"media_naming\"\n    | \"media_naming_api\"\n    | \"delete_unmanaged_custom_formats\"\n    | \"delete_unmanaged_quality_profiles\"\n    | \"delete_unmanaged_metadata_profiles\"\n    | \"metadata_profiles\"\n    | \"root_folders\"\n    | \"delay_profiles\"\n    | \"download_clients\"\n  >\n>;\n\nexport type MappedMergedTemplates = MappedTemplates & Required<Pick<MappedTemplates, \"custom_formats\" | \"quality_profiles\">>;\n\nexport const ArrTypeConst = [\"RADARR\", \"SONARR\", \"WHISPARR\", \"READARR\", \"LIDARR\"] as const;\nexport type ArrType = (typeof ArrTypeConst)[number];\n\nexport type QualityDefinitionsSonarr = \"anime\" | \"series\" | \"custom\";\nexport type QualityDefinitionsRadarr = \"movie\" | \"sqp-streaming\" | \"sqp-uhd\" | \"custom\";\n"
  },
  {
    "path": "src/types/config.types.ts",
    "content": "import { ConfigarrCF } from \"./common.types\";\nimport { TrashCF, TrashQualityDefinitionQuality, TrashScores } from \"./trashguide.types\";\n\nexport type CustomFormatDefinitions = (TrashCF | ConfigarrCF)[];\n\nexport type InputConfigSchema = {\n  trashGuideUrl?: string;\n  trashRevision?: string;\n  recyclarrConfigUrl?: string;\n  recyclarrRevision?: string;\n  localCustomFormatsPath?: string;\n  localConfigTemplatesPath?: string;\n  // @experimental since v1.12.0\n  enableFullGitClone?: boolean;\n  /**\n   * Enable anonymous telemetry tracking of feature usage\n   * @default false\n   */\n  telemetry?: boolean;\n  customFormatDefinitions?: CustomFormatDefinitions;\n  /**\n   * Enable compatibility mode for TRaSH-Guides changes from Feb 2026.\n   * When true: Uses old behavior for both CF groups (exclude semantics) and quality ordering (with reversal).\n   * When false (default): Uses new behavior - CF groups use include semantics, quality ordering matches display order.\n   * @see https://github.com/TRaSH-Guides/Guides/commit/2994a7979d8036a7908a92e2cd286054fd4fcc1b\n   * @default false\n   * @temporary This option will be removed in a future version\n   */\n  compatibilityTrashGuide20260219Enabled?: boolean;\n  /**\n   * Silences warnings emitted when a Quality Profile contains CustomFormats that TRaSH-Guides\n   * marks as mutually exclusive in `conflicts.json`. Sync behavior itself is not changed — only\n   * the log output is suppressed. Useful when conflicting CFs are intentionally configured.\n   * @default false\n   */\n  silenceTrashConflictWarnings?: boolean;\n\n  sonarr?: Record<string, InputConfigArrInstance>;\n  sonarrEnabled?: boolean;\n\n  radarr?: Record<string, InputConfigArrInstance>;\n  radarrEnabled?: boolean;\n\n  whisparr?: Record<string, InputConfigArrInstance>;\n  whisparrEnabled?: boolean;\n\n  readarr?: Record<string, InputConfigArrInstance>;\n  readarrEnabled?: boolean;\n\n  lidarr?: Record<string, InputConfigArrInstance>;\n  lidarrEnabled?: boolean;\n};\n\nexport type InputConfigCustomFormat = {\n  trash_ids?: string[];\n  /**\n   * @deprecated replaced with assign_scores_to\n   */\n  quality_profiles?: { name: string; score?: number; use_default_score?: boolean }[];\n  assign_scores_to?: { name: string; score?: number; use_default_score?: boolean }[];\n};\n\nexport type InputConfigCustomFormatGroup = {\n  trash_guide?: { id: string; include_unrequired?: boolean }[];\n  assign_scores_to?: { name: string; score?: number }[];\n};\n\nexport type InputConfigRootFolderLidarr = {\n  path: string;\n  name: string;\n  metadata_profile: string;\n  quality_profile: string;\n  monitor?: \"all\" | \"future\" | \"missing\" | \"existing\" | \"latest\" | \"first\" | \"none\" | \"unknown\";\n  monitor_new_album?: \"all\" | \"none\" | \"new\";\n  tags?: string[];\n};\n\nexport type InputConfigRootFolderReadarr = {\n  path: string;\n  name: string;\n  metadata_profile: string;\n  quality_profile: string;\n  monitor?: \"all\" | \"future\" | \"missing\" | \"existing\" | \"latest\" | \"first\" | \"none\" | \"unknown\";\n  monitor_new_items?: \"all\" | \"none\" | \"new\";\n  tags?: string[];\n  // Calibre integration (optional)\n  is_calibre_library?: boolean;\n  calibre_host?: string;\n  calibre_port?: number;\n  calibre_url_base?: string;\n  calibre_username?: string;\n  calibre_password?: string;\n  calibre_library?: string;\n  calibre_output_format?: string;\n  calibre_output_profile?: string;\n  calibre_use_ssl?: boolean;\n};\n\nexport type InputConfigRootFolderGeneric = string;\n\nexport type InputConfigRootFolder = InputConfigRootFolderGeneric | InputConfigRootFolderLidarr | InputConfigRootFolderReadarr;\n\nexport type InputConfigDownloadClientConfig = {\n  /**\n   * Folders where download clients store downloads\n   */\n  download_client_working_folders?: string;\n  /**\n   * Enable completed download handling\n   * @default true\n   */\n  enable_completed_download_handling?: boolean;\n  /**\n   * Automatically redownload failed downloads\n   * @default false\n   */\n  auto_redownload_failed?: boolean;\n  /**\n   * Automatically redownload failed downloads from interactive search\n   * @default false\n   */\n  auto_redownload_failed_from_interactive_search?: boolean;\n  /**\n   * Radarr only: Check interval for finished downloads (in minutes)\n   */\n  check_for_finished_download_interval?: number;\n};\n\n/**\n * Configuration for a single remote path mapping\n * @experimental since v1.20.0\n */\nexport interface InputConfigRemotePath {\n  host: string;\n  remote_path: string;\n  local_path: string;\n}\n\nexport type InputConfigArrInstance = {\n  base_url: string;\n  api_key: string;\n  /**\n   * since v1.11.0\n   */\n  enabled?: boolean;\n  /**\n   * since v1.12.0\n   * Deletes all CustomFormats which are not defined in any qualityprofile\n   */\n  delete_unmanaged_custom_formats?: {\n    enabled: boolean;\n    /**\n     * Names of custom formats to ignore deleting\n     */\n    ignore?: string[];\n  };\n  /**\n   * since v1.18.0\n   * Deletes all unmanaged Quality Profile\n   */\n  delete_unmanaged_quality_profiles?: {\n    enabled: boolean;\n    /**\n     * Names of quality profiles to ignore deleting\n     */\n    ignore?: string[];\n  };\n  quality_definition?: {\n    type?: string;\n    preferred_ratio?: number; // 0.0 - 1.0\n    // @experimental\n    qualities?: TrashQualityDefinitionQuality[];\n  };\n  include?: InputConfigIncludeItem[];\n  /**\n   * @experimental since v1.12.0\n   */\n  custom_format_groups?: InputConfigCustomFormatGroup[];\n  custom_formats?: InputConfigCustomFormat[];\n  // TODO this is not correct. The profile can be added partly -> InputConfigQualityProfile\n  quality_profiles: ConfigQualityProfile[];\n  /* @experimental */\n  media_management?: MediaManagementType;\n  /* @experimental */\n  ui_config?: UiConfigType;\n  /* @experimental */\n  media_naming_api?: MediaNamingApiType;\n  renameQualityProfiles?: { from: string; to: string }[];\n  cloneQualityProfiles?: { from: string; to: string }[];\n\n  // this is recyclarr specific: https://recyclarr.dev/wiki/yaml/config-reference/media-naming/\n  media_naming?: MediaNamingType;\n\n  /**\n   * Optional metadata profiles (Lidarr / Readarr only).\n   * Kept close to each Arr application's native MetadataProfileResource.\n   */\n  metadata_profiles?: InputConfigMetadataProfile[];\n\n  /**\n   * Deletes all metadata profiles that are present on the server but not defined\n   * in this configuration. Can be further narrowed using the ignore list.\n   */\n  delete_unmanaged_metadata_profiles?: {\n    enabled: boolean;\n    /**\n     * Names of metadata profiles that should never be deleted automatically.\n     * Can be specified as an array of strings.\n     */\n    ignore?: string[];\n  };\n\n  /**\n   * @experimental since v1.14.0\n   */\n  root_folders?: InputConfigRootFolder[];\n  /**\n   * @experimental since v1.14.0\n   */\n  delay_profiles?: {\n    default?: InputConfigDelayProfile;\n    additional?: InputConfigDelayProfile[];\n  };\n  /**\n   * @experimental since v1.19.0\n   * Download clients configuration\n   */\n  download_clients?: {\n    /**\n     * Array of download client configurations\n     */\n    data?: InputConfigDownloadClient[];\n    /**\n     * Always update password fields even when they match the server (to ensure passwords are current)\n     * @default false\n     */\n    update_password?: boolean;\n    /**\n     * Delete unmanaged download clients\n     * @default false\n     */\n    delete_unmanaged?: {\n      enabled: boolean;\n      /**\n       * Names of download clients to ignore deleting\n       */\n      ignore?: string[];\n    };\n    /**\n     * Global download client configuration\n     */\n    config?: InputConfigDownloadClientConfig;\n    /**\n     * Remote path mappings for download clients\n     * @experimental since v1.20.0\n     */\n    remote_paths?: InputConfigRemotePath[];\n    /**\n     * Delete unmanaged remote path mappings\n     * @experimental since v1.20.0\n     * @default false\n     */\n    delete_unmanaged_remote_paths?: boolean;\n  };\n} & Pick<InputConfigSchema, \"customFormatDefinitions\">;\n\nexport type InputConfigDelayProfile = {\n  enableUsenet?: boolean;\n  enableTorrent?: boolean;\n  preferredProtocol?: string;\n  usenetDelay?: number;\n  torrentDelay?: number;\n  bypassIfHighestQuality?: boolean;\n  bypassIfAboveCustomFormatScore?: boolean;\n  minimumCustomFormatScore?: number;\n  order?: number;\n  tags?: string[];\n};\n\nexport type InputConfigDownloadClient = {\n  /**\n   * Download client name (must be unique)\n   */\n  name: string;\n  /**\n   * Download client type/implementation (e.g., \"qbittorrent\", \"transmission\", \"sabnzbd\")\n   */\n  type: string;\n  /**\n   * Whether the download client is enabled\n   * @default true\n   */\n  enable?: boolean;\n  /**\n   * Download client priority\n   * @default 1\n   */\n  priority?: number;\n  /**\n   * Remove completed downloads\n   * @default true\n   */\n  remove_completed_downloads?: boolean;\n  /**\n   * Remove failed downloads\n   * @default true\n   */\n  remove_failed_downloads?: boolean;\n  /**\n   * Field configuration (host, port, username, password, etc.)\n   */\n  fields?: Record<string, any>;\n  /**\n   * Tags to apply to this download client (can be tag names or IDs)\n   * Tag names will be automatically resolved to IDs, creating new tags if needed\n   */\n  tags?: (string | number)[];\n};\n\nexport type MediaManagementType = {\n  // APIs not consistent across different *arrs. Keeping empty or generic\n};\n\nexport type UiConfigType = {\n  // APIs not consistent across different *arrs. Keeping empty or generic\n};\n\n// HINT: Experimental\nexport type MediaNamingApiType = {\n  // APIs not consistent across different *arrs. Keeping empty or generic\n};\n\nexport type MediaNamingType = {\n  // radarr\n  folder?: string;\n  movie?: {\n    rename?: boolean;\n    standard?: string;\n  };\n\n  // sonarr\n  series?: string;\n  season?: string;\n  episodes?: {\n    rename?: boolean;\n    standard?: string;\n    daily?: string;\n    anime?: string;\n  };\n};\n\nexport type InputConfigQualityProfile = {\n  name: string;\n  reset_unmatched_scores?: {\n    enabled: boolean;\n    except?: string[];\n  };\n  upgrade?:\n    | {\n        allowed: true;\n        until_quality: string;\n        until_score: number;\n        min_format_score?: number; // default 1\n      }\n    | {\n        allowed: false;\n        until_quality?: string;\n        until_score?: number;\n        min_format_score?: number;\n      };\n  min_format_score?: number;\n  score_set?: keyof TrashScores;\n  quality_sort?: string;\n  language?: string;\n  qualities?: InputConfigQualityProfileItem[];\n};\n\nexport type InputConfigQualityProfileItem = {\n  name: string;\n  qualities?: string[];\n  enabled?: boolean;\n};\n\n// Lidarr-specific metadata profile config\nexport type InputConfigLidarrMetadataProfile = {\n  name: string;\n  primary_types?: string[];\n  secondary_types?: string[];\n  release_statuses?: string[];\n};\n\n// Readarr-specific metadata profile config\nexport type InputConfigReadarrMetadataProfile = {\n  name: string;\n  min_popularity?: number;\n  skip_missing_date?: boolean;\n  skip_missing_isbn?: boolean;\n  skip_parts_and_sets?: boolean;\n  skip_secondary_series?: boolean;\n  allowed_languages?: string[] | null;\n  min_pages?: number | null;\n  must_not_contain?: string[];\n};\n\n// Union type for backward compatibility\nexport type InputConfigMetadataProfile = InputConfigLidarrMetadataProfile | InputConfigReadarrMetadataProfile;\n\nexport type InputConfigIncludeItem = {\n  // depends on source what this actually is. Can be the filename -> recyclarr or id in the files -> trash\n  template: string;\n  source?: \"TRASH\" | \"RECYCLARR\";\n  /**\n   * Optional preferred ratio (0.0 - 1.0) applied when this include resolves to a\n   * TRaSH quality definition. Has no effect for quality profile includes.\n   */\n  preferred_ratio?: number;\n};\n\nexport type ConfigSchema = InputConfigSchema;\n\nexport type ConfigCustomFormat = Pick<InputConfigCustomFormat, \"trash_ids\"> & Pick<InputConfigCustomFormat, \"assign_scores_to\">;\n\nexport type ConfigCustomFormatList = Pick<ConfigArrInstance, \"custom_formats\">;\n\nexport type ConfigArrInstance = OmitTyped<InputConfigArrInstance, \"custom_formats\" | \"include\" | \"quality_profiles\"> & {\n  include?: ConfigIncludeItem[];\n  custom_formats: ConfigCustomFormat[];\n  quality_profiles: ConfigQualityProfile[];\n  /**\n   * Metadata profiles are kept in configuration shape; they are translated to\n   * the concrete Arr application's MetadataProfileResource in the feature layer.\n   */\n  metadata_profiles?: InputConfigMetadataProfile[];\n};\n\nexport type ConfigQualityProfile = OmitTyped<Required<InputConfigQualityProfile>, \"qualities\" | \"reset_unmatched_scores\" | \"language\"> & {\n  qualities: ConfigQualityProfileItem[];\n  reset_unmatched_scores?: InputConfigQualityProfile[\"reset_unmatched_scores\"];\n} & Pick<InputConfigQualityProfile, \"language\">;\n\nexport type ConfigQualityProfileItem = InputConfigQualityProfileItem;\n\nexport type ConfigIncludeItem = OmitTyped<InputConfigIncludeItem, \"source\"> & {\n  source: InputConfigIncludeItem[\"source\"];\n};\n\n// TODO maybe reduce\nexport type InputConfigInstance = OmitTyped<InputConfigArrInstance, \"api_key\" | \"base_url\">;\nexport type MergedConfigInstance = OmitTyped<ConfigArrInstance, \"api_key\" | \"base_url\" | \"include\">;\n"
  },
  {
    "path": "src/types/download-client.types.ts",
    "content": "import { InputConfigDownloadClient } from \"./config.types\";\nimport type {\n  Field as LidarrDownloadClientField,\n  DownloadClientResource as LidarrDownloadClientResource,\n  TagResource as LidarrTagResource,\n} from \"../__generated__/lidarr/data-contracts\";\nimport type {\n  Field as RadarrDownloadClientField,\n  DownloadClientResource as RadarrDownloadClientResource,\n  TagResource as RadarrDownloadClientTagResource,\n} from \"../__generated__/radarr/data-contracts\";\nimport type {\n  Field as ReadarrDownloadClientField,\n  DownloadClientResource as ReadarrDownloadClientResource,\n  TagResource as ReadarrTagResource,\n} from \"../__generated__/readarr/data-contracts\";\nimport type {\n  Field as SonarrDownloadClientField,\n  DownloadClientResource as SonarrDownloadClientResource,\n  TagResource as SonarrDownloadClientTagResource,\n} from \"../__generated__/sonarr/data-contracts\";\nimport type {\n  Field as WhisparrDownloadClientField,\n  DownloadClientResource as WhisparrDownloadClientResource,\n  TagResource as WhisparrTagResource,\n} from \"../__generated__/whisparr/data-contracts\";\n\n/**\n * Canonical union of all generator-specific download client resources.\n *\n * All client-facing logic (schema retrieval, diffing, sync, etc.)\n * should depend on this type instead of generator-specific resources.\n */\nexport type DownloadClientResource =\n  | RadarrDownloadClientResource\n  | SonarrDownloadClientResource\n  | LidarrDownloadClientResource\n  | ReadarrDownloadClientResource\n  | WhisparrDownloadClientResource;\n\n/**\n * Canonical union of all generator-specific download client fields.\n *\n * Use this in any code that works with download client configuration\n * fields across different Arr implementations.\n */\nexport type DownloadClientField =\n  | RadarrDownloadClientField\n  | SonarrDownloadClientField\n  | LidarrDownloadClientField\n  | ReadarrDownloadClientField\n  | WhisparrDownloadClientField;\n\n/**\n * Canonical union of all generator-specific tag resources used by\n * download clients. Prefer this over generator-specific tag types\n * in client-facing code.\n */\nexport type DownloadClientTagResource =\n  | RadarrDownloadClientTagResource\n  | SonarrDownloadClientTagResource\n  | LidarrTagResource\n  | ReadarrTagResource\n  | WhisparrTagResource;\n\nexport interface ValidationResult {\n  valid: boolean;\n  errors: string[];\n  warnings: string[];\n}\n\nexport interface ConnectionTestResult {\n  success: boolean;\n  message?: string;\n  error?: string;\n}\n\nexport type DownloadClientDiff = {\n  create: InputConfigDownloadClient[];\n  update: { config: InputConfigDownloadClient; server: DownloadClientResource; partialUpdate: boolean }[];\n  unchanged: { config: InputConfigDownloadClient; server: DownloadClientResource }[];\n  deleted: DownloadClientResource[];\n};\n\nexport interface DownloadClientSyncResult {\n  added: number;\n  updated: number;\n  removed: number;\n}\n\nexport type TagLike = { id?: number; label?: string | null };\n"
  },
  {
    "path": "src/types/helper.types.ts",
    "content": "// The default utility type is not typed in the keys\ntype OmitTyped<Obj extends object, Keys extends keyof Obj> = Omit<Obj, Keys>;\n\ntype OmitTyped2<T, K extends keyof T | (string & {}) | (number & {}) | (symbol | {})> = { [P in Exclude<keyof T, K>]: T[P] };\n\ntype Subset<K, T extends K> = T;\n"
  },
  {
    "path": "src/types/merged.types.ts",
    "content": "import {\n  QualityDefinitionResource as QDRRadarr,\n  CustomFormatResource as RadarrCustomFormatResource,\n  CustomFormatSpecificationSchema as RadarrCustomFormatSpecificationSchema,\n  ProfileFormatItemResource as RadarrProfileFormatItemResource,\n  QualityProfileQualityItemResource as RadarrQualityProfileQualityItemResource,\n  QualityProfileResource as RadarrQualityProfileResource,\n  RootFolderResource as RadarrRootFolderResource,\n  TagResource as RadarrTagResource,\n} from \"../__generated__/radarr/data-contracts\";\nimport {\n  QualityDefinitionResource as QDRSonarr,\n  CustomFormatResource as SonarrCustomFormatResource,\n  CustomFormatSpecificationSchema as SonarrCustomFormatSpecificationSchema,\n  ProfileFormatItemResource as SonarrProfileFormatItemResource,\n  QualityProfileQualityItemResource as SonarrQualityProfileQualityItemResource,\n  QualityProfileResource as SonarrQualityProfileResource,\n  RootFolderResource as SonarrRootFolderResource,\n} from \"../__generated__/sonarr/data-contracts\";\n\n// Those types are only to make the API client unified usable.\n// Sonarr and Radarr slightly differ in API fields and therefore at the moment we can ignore those changes.\n// If someday we need specific fields per *arr instance then we have to split the API usage and modify every module.\n\ntype QDRMerged = QDRSonarr & QDRRadarr;\ntype QDRPickedSource = OmitTyped<NonNullable<QDRMerged[\"quality\"]>, \"source\">;\ntype CustomQualitySource<T> = {\n  quality?: T & {\n    source?: string;\n  };\n};\n\ntype OmittedQuality = OmitTyped<QDRMerged, \"quality\">;\n\nexport type MergedQualityDefinitionResource = OmittedQuality & Partial<CustomQualitySource<QDRPickedSource>>;\nexport type MergedCustomFormatResource = SonarrCustomFormatResource & RadarrCustomFormatResource;\nexport type MergedProfileFormatItemResource = SonarrProfileFormatItemResource & RadarrProfileFormatItemResource;\n\ntype QPQIRMerged = SonarrQualityProfileQualityItemResource & RadarrQualityProfileQualityItemResource;\ntype QPQIRPickedSource = OmitTyped<NonNullable<QPQIRMerged[\"quality\"]>, \"source\">;\n\nexport type MergedQualityProfileQualityItemResource = OmitTyped<QPQIRMerged, \"items\" | \"quality\"> &\n  Partial<\n    OmitTyped<QPQIRMerged, \"items\" | \"quality\"> & {\n      items?: MergedQualityProfileQualityItemResource[] | null;\n      quality?: QPQIRPickedSource & { source?: string };\n    }\n  >;\n\ntype QPRMerged = SonarrQualityProfileResource & RadarrQualityProfileResource;\n\nexport type MergedQualityProfileResource = OmitTyped<QPRMerged, \"items\"> &\n  Partial<\n    OmitTyped<QPRMerged, \"items\"> & {\n      items?: MergedQualityProfileQualityItemResource[] | null;\n    }\n  >;\n\nexport type MergedCustomFormatSpecificationSchema = RadarrCustomFormatSpecificationSchema & SonarrCustomFormatSpecificationSchema;\nexport type MergedRootFolderResource = SonarrRootFolderResource & RadarrRootFolderResource;\nexport type MergedDelayProfileResource = import(\"../__generated__/sonarr/data-contracts\").DelayProfileResource &\n  import(\"../__generated__/radarr/data-contracts\").DelayProfileResource;\n\nexport type MergedTagResource = RadarrTagResource;\n"
  },
  {
    "path": "src/types/recyclarr.types.ts",
    "content": "import { ArrType } from \"./common.types\";\nimport { ConfigArrInstance, InputConfigCustomFormat } from \"./config.types\";\n\nexport type RecyclarrCustomFormats = Partial<Pick<InputConfigCustomFormat, \"trash_ids\" | \"quality_profiles\" | \"assign_scores_to\">> & {};\n\nexport type RecyclarrConfigInstance = OmitTyped<ConfigArrInstance, \"custom_formats\"> & {\n  custom_formats: RecyclarrCustomFormats[];\n};\n\nexport type RecyclarrTemplates = Partial<\n  Pick<RecyclarrConfigInstance, \"quality_definition\" | \"custom_formats\" | \"include\" | \"quality_profiles\">\n>;\n\nexport type RecyclarrArrSupported = Subset<ArrType, \"RADARR\" | \"SONARR\">;\n"
  },
  {
    "path": "src/types/trashguide.types.ts",
    "content": "import { ArrType, ArrTypeConst, CFIDToConfigGroup, ImportCF } from \"./common.types\";\n\nexport type TrashQualityDefinitionQuality = {\n  quality: string;\n  title?: string;\n  min: number;\n  preferred: number;\n  max: number;\n};\n\nexport type TrashQualityDefinition = {\n  trash_id: string;\n  type: string;\n  qualities: TrashQualityDefinitionQuality[];\n};\n\nexport type TrashScores = {\n  default?: number;\n  \"anime-sonarr\"?: number;\n  \"anime-radarr\"?: number;\n  \"sqp-1-1080p\"?: number;\n  \"sqp-1-2160p\"?: number;\n  \"sqp-2\"?: number;\n  \"sqp-3\"?: number;\n  \"sqp-4\"?: number;\n  \"sqp-5\"?: number;\n  \"french-vostfr\"?: number;\n  german?: number;\n};\n\nexport type TrashCFMeta = {\n  trash_id: string;\n  trash_scores?: TrashScores;\n  trash_regex?: string;\n  trash_description?: string;\n};\n\nexport type TrashCF = TrashCFMeta & ImportCF;\n\ntype TrashQPItem = {\n  name: string;\n  allowed: boolean;\n  items?: string[];\n};\n\nexport type TrashQP = {\n  trash_id: string;\n  name: string;\n  trash_score_set: keyof Required<TrashScores>;\n  language?: string;\n  upgradeAllowed: boolean;\n  cutoff: string;\n  minFormatScore: number;\n  cutoffFormatScore: number;\n  items: TrashQPItem[];\n  formatItems: {\n    [key: string]: string;\n  };\n};\n\nexport type TrashCFSpF = { min: number; max: number; exceptLanguage: boolean; value: any };\n\nexport const TrashArrSupportedConst = [\"RADARR\", \"SONARR\"] as const satisfies readonly ArrType[];\nexport type TrashArrSupported = (typeof TrashArrSupportedConst)[number];\n\nexport type TrashRadarrNaming = {\n  folder: {\n    [key: string]: string;\n  };\n  file: {\n    [key: string]: string;\n  };\n};\n\nexport type TrashSonarrNaming = {\n  season: {\n    [key: string]: string;\n  };\n  series: {\n    [key: string]: string;\n  };\n  episodes: {\n    standard: {\n      [key: string]: string;\n    };\n\n    daily: {\n      [key: string]: string;\n    };\n\n    anime: {\n      [key: string]: string;\n    };\n  };\n};\n\nexport type TrashCache = {\n  SONARR: {\n    qualityProfiles: Map<string, TrashQP>;\n    customFormats: CFIDToConfigGroup;\n    customFormatsGroups: TrashCFGroupMapping;\n    qualityDefinition: {\n      series: TrashQualityDefinition;\n      anime: TrashQualityDefinition;\n    };\n    naming: TrashSonarrNaming | null;\n    conflicts: TrashCFConflict[];\n  };\n  RADARR: {\n    qualityProfiles: Map<string, TrashQP>;\n    customFormats: CFIDToConfigGroup;\n    customFormatsGroups: TrashCFGroupMapping;\n    qualityDefinition: {\n      movie: TrashQualityDefinition;\n    };\n    naming: TrashRadarrNaming | null;\n    conflicts: TrashCFConflict[];\n  };\n};\n\ntype TrashCFGItem = {\n  name: string;\n  trash_id: string;\n  /**\n   * Required CFs for the profile. Will be added\n   */\n  required: boolean;\n  /**\n   * Selection if should be added even if required is false\n   */\n  default?: boolean;\n};\n\nexport type TrashCustomFormatGroups = {\n  name: string;\n  trash_id: string;\n  trash_description?: string;\n  /**\n   * If this group should be added in always for TRaSH-Guide profiles\n   * Should also be an boolean in theory but is an string in the guide\n   */\n  default?: string;\n  custom_formats: TrashCFGItem[];\n  quality_profiles?: {\n    /**\n     * @deprecated Use include instead. Kept for compatibility with old TRaSH-Guides versions.\n     * Exclude profiles for which this group should not be applied if enabled in default.\n     */\n    exclude?: Record<string, string>; // name to id like: \"HD Bluray + WEB\": \"d1d67249d3890e49bc12e275d989a7e9\"\n    /**\n     * Profiles for which this group should be applied.\n     * Only profiles listed here will receive the CFs from this group.\n     */\n    include?: Record<string, string>; // name to id like: \"HD Bluray + WEB\": \"d1d67249d3890e49bc12e275d989a7e9\"\n  };\n};\n\nexport type TrashCFGroupMapping = Map<string, TrashCustomFormatGroups>;\n\n/**\n * Runtime representation of one TRaSH conflict group (mutually exclusive custom formats).\n * Built from conflicts.json by normalizing each `custom_formats` array entry.\n */\nexport type TrashCFConflict = {\n  trash_id: string;\n  name: string;\n  trash_description?: string;\n  custom_formats: Array<{\n    trash_id: string;\n    name: string;\n  }>;\n};\n"
  },
  {
    "path": "src/uiConfigs/uiConfig.types.ts",
    "content": "import { ArrType } from \"../types/common.types\";\n\n/**\n * Result of a UI config sync operation\n */\nexport interface UiConfigSyncResult {\n  updated: boolean;\n  arrType: ArrType;\n}\n\n/**\n * Minimal UI config resource shape with required fields for sync operations.\n * The actual server response contains many more fields depending on the *arr type.\n */\nexport interface UiConfigResource {\n  id: number;\n  [key: string]: unknown;\n}\n"
  },
  {
    "path": "src/uiConfigs/uiConfigSyncer.test.ts",
    "content": "import { describe, expect, test, vi, beforeEach, afterEach } from \"vitest\";\nimport { syncUiConfig } from \"./uiConfigSyncer\";\nimport { getSpecificClient } from \"../clients/unified-client\";\nimport { getEnvs } from \"../env\";\n\n// Mock dependencies\nvi.mock(\"../clients/unified-client\", () => ({\n  getSpecificClient: vi.fn(),\n}));\n\nvi.mock(\"../env\", () => ({\n  getEnvs: vi.fn(() => ({ DRY_RUN: false })),\n  getHelpers: vi.fn(() => ({\n    configLocation: \"/config/config.yml\",\n    secretLocation: \"/config/secrets.yml\",\n    repoPath: \"/repos\",\n  })),\n}));\n\nvi.mock(\"../logger\", () => ({\n  logger: {\n    debug: vi.fn(),\n    info: vi.fn(),\n    error: vi.fn(),\n  },\n}));\n\ndescribe(\"uiConfigSyncer\", () => {\n  const mockGetUiConfig = vi.fn();\n  const mockUpdateUiConfig = vi.fn();\n\n  beforeEach(() => {\n    vi.clearAllMocks();\n    vi.mocked(getSpecificClient).mockReturnValue({\n      getUiConfig: mockGetUiConfig,\n      updateUiConfig: mockUpdateUiConfig,\n    } as any);\n  });\n\n  afterEach(() => {\n    vi.resetAllMocks();\n  });\n\n  describe(\"syncUiConfig\", () => {\n    test(\"should skip when uiConfig is undefined\", async () => {\n      const result = await syncUiConfig(\"RADARR\", undefined);\n\n      expect(result).toEqual({ updated: false, arrType: \"RADARR\" });\n      expect(getSpecificClient).not.toHaveBeenCalled();\n      expect(mockGetUiConfig).not.toHaveBeenCalled();\n    });\n\n    test(\"should handle null config by attempting to sync (null is not undefined)\", async () => {\n      // Note: null is different from undefined - the syncer only skips on undefined\n      const serverConfig = { id: 1, theme: \"light\" };\n\n      mockGetUiConfig.mockResolvedValue(serverConfig);\n\n      // null as config will be treated as a valid config object\n      // The diff calculation will compare null against server config\n      const result = await syncUiConfig(\"SONARR\", null as any);\n\n      expect(getSpecificClient).toHaveBeenCalledWith(\"SONARR\");\n      expect(mockGetUiConfig).toHaveBeenCalled();\n    });\n\n    test(\"should return updated: false when server config is already up-to-date\", async () => {\n      const serverConfig = { id: 1, theme: \"dark\", language: \"en\" };\n      const localConfig = { theme: \"dark\", language: \"en\" };\n\n      mockGetUiConfig.mockResolvedValue(serverConfig);\n\n      const result = await syncUiConfig(\"RADARR\", localConfig);\n\n      expect(result).toEqual({ updated: false, arrType: \"RADARR\" });\n      expect(mockUpdateUiConfig).not.toHaveBeenCalled();\n    });\n\n    test(\"should detect and apply changes when config differs\", async () => {\n      const serverConfig = { id: 1, theme: \"light\", language: \"en\" };\n      const localConfig = { theme: \"dark\" };\n\n      mockGetUiConfig.mockResolvedValue(serverConfig);\n      mockUpdateUiConfig.mockResolvedValue({ ...serverConfig, ...localConfig });\n\n      const result = await syncUiConfig(\"RADARR\", localConfig);\n\n      expect(result).toEqual({ updated: true, arrType: \"RADARR\" });\n      expect(mockUpdateUiConfig).toHaveBeenCalledWith(\"1\", { id: 1, theme: \"dark\", language: \"en\" });\n    });\n\n    test(\"should not update in dry-run mode\", async () => {\n      vi.mocked(getEnvs).mockReturnValue({ DRY_RUN: true } as any);\n\n      const serverConfig = { id: 1, theme: \"light\" };\n      const localConfig = { theme: \"dark\" };\n\n      mockGetUiConfig.mockResolvedValue(serverConfig);\n\n      const result = await syncUiConfig(\"SONARR\", localConfig);\n\n      expect(result).toEqual({ updated: true, arrType: \"SONARR\" });\n      expect(mockUpdateUiConfig).not.toHaveBeenCalled();\n    });\n\n    test(\"should throw error when serverConfig.id is missing\", async () => {\n      const serverConfig = { theme: \"light\" }; // No id field\n      const localConfig = { theme: \"dark\" };\n\n      mockGetUiConfig.mockResolvedValue(serverConfig);\n\n      await expect(syncUiConfig(\"RADARR\", localConfig)).rejects.toThrow(\n        \"UI config sync failed for RADARR: UI config for RADARR is missing required 'id' field\",\n      );\n    });\n\n    test(\"should throw error when serverConfig.id is null\", async () => {\n      const serverConfig = { id: null, theme: \"light\" };\n      const localConfig = { theme: \"dark\" };\n\n      mockGetUiConfig.mockResolvedValue(serverConfig);\n\n      await expect(syncUiConfig(\"SONARR\", localConfig)).rejects.toThrow(\"missing required 'id' field\");\n    });\n\n    test(\"should throw error when serverConfig.id is 0 (falsy)\", async () => {\n      const serverConfig = { id: 0, theme: \"light\" };\n      const localConfig = { theme: \"dark\" };\n\n      mockGetUiConfig.mockResolvedValue(serverConfig);\n\n      await expect(syncUiConfig(\"LIDARR\", localConfig)).rejects.toThrow(\"missing required 'id' field\");\n    });\n\n    test(\"should propagate client errors with context\", async () => {\n      mockGetUiConfig.mockRejectedValue(new Error(\"Network error\"));\n\n      await expect(syncUiConfig(\"RADARR\", { theme: \"dark\" })).rejects.toThrow(\"UI config sync failed for RADARR: Network error\");\n    });\n\n    test(\"should handle non-Error thrown objects\", async () => {\n      mockGetUiConfig.mockRejectedValue(\"String error\");\n\n      await expect(syncUiConfig(\"RADARR\", { theme: \"dark\" })).rejects.toThrow(\"UI config sync failed for RADARR: String error\");\n    });\n\n    test(\"should work with different arr types\", async () => {\n      const arrTypes = [\"RADARR\", \"SONARR\", \"LIDARR\", \"READARR\", \"WHISPARR\"] as const;\n\n      for (const arrType of arrTypes) {\n        vi.clearAllMocks();\n        const serverConfig = { id: 1, theme: \"light\" };\n        const localConfig = { theme: \"dark\" };\n\n        mockGetUiConfig.mockResolvedValue(serverConfig);\n        mockUpdateUiConfig.mockResolvedValue({ ...serverConfig, ...localConfig });\n\n        const result = await syncUiConfig(arrType, localConfig);\n\n        expect(result).toEqual({ updated: true, arrType });\n        expect(getSpecificClient).toHaveBeenCalledWith(arrType);\n      }\n    });\n\n    test(\"should merge server config with local config on update\", async () => {\n      const serverConfig = {\n        id: 1,\n        theme: \"light\",\n        language: \"en\",\n        firstDayOfWeek: 0,\n        calendarWeekColumnHeader: \"ddd\",\n      };\n      const localConfig = {\n        theme: \"dark\",\n        firstDayOfWeek: 1,\n      };\n\n      mockGetUiConfig.mockResolvedValue(serverConfig);\n      mockUpdateUiConfig.mockResolvedValue({});\n\n      await syncUiConfig(\"RADARR\", localConfig);\n\n      expect(mockUpdateUiConfig).toHaveBeenCalledWith(\"1\", {\n        id: 1,\n        theme: \"dark\",\n        language: \"en\",\n        firstDayOfWeek: 1,\n        calendarWeekColumnHeader: \"ddd\",\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/uiConfigs/uiConfigSyncer.ts",
    "content": "import { getSpecificClient } from \"../clients/unified-client\";\nimport { logger } from \"../logger\";\nimport { ArrType } from \"../types/common.types\";\nimport type { UiConfigType } from \"../types/config.types\";\nimport { UiConfigSyncResult, UiConfigResource } from \"./uiConfig.types\";\nimport { compareObjectsCarr } from \"../util\";\nimport { getEnvs } from \"../env\";\n\n/**\n * Type guard to validate that server config has the required id field\n */\nfunction hasValidId(config: Record<string, unknown>): config is UiConfigResource {\n  return typeof config.id === \"number\" && config.id > 0;\n}\n\n/**\n * Sync UI config for a specific *Arr instance\n */\nexport async function syncUiConfig(arrType: ArrType, uiConfig: UiConfigType | undefined): Promise<UiConfigSyncResult> {\n  // If ui_config is undefined/not present, skip management entirely\n  if (uiConfig === undefined) {\n    logger.debug(`No UI config specified for ${arrType}`);\n    return { updated: false, arrType };\n  }\n\n  try {\n    // Get specific client for this arrType - TypeScript infers the correct type\n    const client = getSpecificClient(arrType);\n\n    // Fetch current server UI config\n    logger.debug(`Fetching UI config from ${arrType}...`);\n    const serverConfig = await client.getUiConfig();\n\n    // Cast to generic record for comparison - generated types don't have index signatures\n    const serverConfigRecord = serverConfig as Record<string, unknown>;\n\n    // Calculate diff directly using compareObjectsCarr\n    const { changes, equal } = compareObjectsCarr(serverConfigRecord, uiConfig);\n\n    if (equal) {\n      logger.info(`UI config for ${arrType} is already up-to-date`);\n      return { updated: false, arrType };\n    }\n\n    logger.info(`UI config changes detected for ${arrType}: ${changes.length} differences`);\n    logger.debug(`UI config changes: ${changes.join(\", \")}`);\n\n    // Respect dry-run mode\n    if (getEnvs().DRY_RUN) {\n      logger.info(`DryRun: Would update UI config for ${arrType}`);\n      return { updated: true, arrType };\n    }\n\n    // Validate server config has required id field\n    if (!hasValidId(serverConfigRecord)) {\n      throw new Error(`UI config for ${arrType} is missing required 'id' field`);\n    }\n\n    // Execute update - merge server config with local config\n    const updatedConfig = {\n      ...serverConfig,\n      ...uiConfig,\n    };\n\n    await client.updateUiConfig(serverConfigRecord.id.toString(), updatedConfig);\n    logger.info(`Successfully updated UI config for ${arrType}`);\n    return { updated: true, arrType };\n  } catch (error: unknown) {\n    const errorMessage = error instanceof Error ? error.message : String(error);\n    logger.error(`Failed to sync UI config for ${arrType}: ${errorMessage}`);\n    throw new Error(`UI config sync failed for ${arrType}: ${errorMessage}`);\n  }\n}\n"
  },
  {
    "path": "src/url-template-importer.test.ts",
    "content": "import { KyInstance } from \"ky\";\nimport { beforeEach, describe, expect, test, vi } from \"vitest\";\nimport yaml from \"yaml\";\nimport { MappedTemplates } from \"./types/common.types\";\nimport { TrashQP } from \"./types/trashguide.types\";\nimport { isUrl, loadTemplateFromUrl } from \"./url-template-importer\";\n\n// Mock ky for URL template tests\nconst mockKyGet = vi.hoisted(() => vi.fn());\nvi.mock(\"ky\", () => {\n  const mockKy = vi.fn() as unknown as KyInstance;\n  mockKy.get = mockKyGet;\n  return {\n    default: mockKy,\n  };\n});\n\n// Mock logger\nvi.mock(\"./logger\", () => ({\n  logger: {\n    debug: vi.fn(),\n    info: vi.fn(),\n    warn: vi.fn(),\n    error: vi.fn(),\n  },\n}));\n\ndescribe(\"url-template-importer\", () => {\n  beforeEach(() => {\n    vi.resetAllMocks();\n    mockKyGet.mockReset();\n  });\n\n  describe(\"isUrl\", () => {\n    test(\"should detect HTTP URLs\", () => {\n      expect(isUrl(\"http://example.com/template.yml\")).toBe(true);\n    });\n\n    test(\"should detect HTTPS URLs\", () => {\n      expect(isUrl(\"https://example.com/template.yml\")).toBe(true);\n    });\n\n    test(\"should reject non-URL strings\", () => {\n      expect(isUrl(\"template-name\")).toBe(false);\n      expect(isUrl(\"local-template\")).toBe(false);\n      expect(isUrl(\"\")).toBe(false);\n    });\n\n    test(\"should reject invalid URLs\", () => {\n      expect(isUrl(\"not a url\")).toBe(false);\n      expect(isUrl(\"ftp://example.com\")).toBe(false);\n    });\n  });\n\n  describe(\"loadTemplateFromUrl\", () => {\n    describe(\"Recyclarr format (YAML)\", () => {\n      test(\"should load template from URL\", async () => {\n        const template: MappedTemplates = {\n          custom_formats: [{ trash_ids: [\"cf-url\"], assign_scores_to: [{ name: \"profile\" }] }],\n          quality_profiles: [],\n        };\n\n        mockKyGet.mockResolvedValue({\n          text: async () => yaml.stringify(template),\n        });\n\n        const result = await loadTemplateFromUrl(\"https://example.com/template.yml\");\n\n        expect(mockKyGet).toHaveBeenCalledWith(\"https://example.com/template.yml\", { timeout: 30000 });\n        expect(result).not.toBeNull();\n        expect((result as MappedTemplates).custom_formats).toHaveLength(1);\n        expect((result as MappedTemplates).custom_formats![0]!.trash_ids).toEqual([\"cf-url\"]);\n      });\n\n      test(\"should handle URL template loading failure gracefully\", async () => {\n        mockKyGet.mockRejectedValue(new Error(\"Network error\"));\n\n        const result = await loadTemplateFromUrl(\"https://example.com/nonexistent.yml\");\n\n        expect(mockKyGet).toHaveBeenCalled();\n        expect(result).toBeNull();\n      });\n\n      test(\"should handle empty URL template content gracefully\", async () => {\n        mockKyGet.mockResolvedValue({\n          text: async () => \"\",\n        });\n\n        const result = await loadTemplateFromUrl(\"https://example.com/empty.yml\");\n\n        expect(mockKyGet).toHaveBeenCalled();\n        expect(result).toBeNull();\n      });\n\n      test(\"should transform custom formats with quality_profiles to assign_scores_to\", async () => {\n        const template: MappedTemplates = {\n          custom_formats: [\n            {\n              trash_ids: [\"cf1\"],\n              quality_profiles: [{ name: \"profile1\", score: 10 }],\n            },\n          ],\n          quality_profiles: [],\n        };\n\n        mockKyGet.mockResolvedValue({\n          text: async () => yaml.stringify(template),\n        });\n\n        const result = await loadTemplateFromUrl(\"https://example.com/template.yml\");\n\n        expect(result).not.toBeNull();\n        const cf = (result as MappedTemplates).custom_formats?.[0];\n        expect(cf).toBeDefined();\n        expect(cf!.assign_scores_to).toEqual([{ name: \"profile1\", score: 10 }]);\n        // quality_profiles is still present but assign_scores_to takes precedence\n        expect(cf!.quality_profiles).toBeDefined();\n      });\n\n      test(\"should handle template without source parameter (defaults to Recyclarr)\", async () => {\n        const template: MappedTemplates = {\n          custom_formats: [],\n          quality_profiles: [],\n        };\n\n        mockKyGet.mockResolvedValue({\n          text: async () => yaml.stringify(template),\n        });\n\n        const result = await loadTemplateFromUrl(\"https://example.com/template.yml\");\n\n        expect(result).not.toBeNull();\n        expect(mockKyGet).toHaveBeenCalled();\n      });\n    });\n\n    describe(\"TRASH format (JSON)\", () => {\n      test(\"should load TRASH template from URL\", async () => {\n        const trashTemplate: TrashQP = {\n          trash_id: \"test-trash-id\",\n          name: \"TRASH Profile\",\n          trash_score_set: \"default\",\n          upgradeAllowed: true,\n          cutoff: \"HDTV-1080p\",\n          minFormatScore: 0,\n          cutoffFormatScore: 1000,\n          items: [{ name: \"HDTV-1080p\", allowed: true }],\n          formatItems: {},\n        };\n\n        mockKyGet.mockResolvedValue({\n          text: async () => JSON.stringify(trashTemplate),\n        });\n\n        const result = await loadTemplateFromUrl(\"https://example.com/trash-template.json\", \"TRASH\");\n\n        expect(mockKyGet).toHaveBeenCalledWith(\"https://example.com/trash-template.json\", { timeout: 30000 });\n        expect(result).not.toBeNull();\n        expect((result as TrashQP).trash_id).toBe(\"test-trash-id\");\n        expect((result as TrashQP).name).toBe(\"TRASH Profile\");\n      });\n\n      test(\"should handle TRASH URL template loading failure gracefully\", async () => {\n        mockKyGet.mockRejectedValue(new Error(\"Network error\"));\n\n        const result = await loadTemplateFromUrl(\"https://example.com/nonexistent-trash.json\", \"TRASH\");\n\n        expect(mockKyGet).toHaveBeenCalled();\n        expect(result).toBeNull();\n      });\n\n      test(\"should reject TRASH template without trash_id\", async () => {\n        const invalidTemplate = {\n          name: \"Invalid Profile\",\n          // missing trash_id\n        };\n\n        mockKyGet.mockResolvedValue({\n          text: async () => JSON.stringify(invalidTemplate),\n        });\n\n        const result = await loadTemplateFromUrl(\"https://example.com/invalid-trash.json\", \"TRASH\");\n\n        expect(mockKyGet).toHaveBeenCalled();\n        expect(result).toBeNull();\n      });\n\n      test(\"should handle empty TRASH template content gracefully\", async () => {\n        mockKyGet.mockResolvedValue({\n          text: async () => \"\",\n        });\n\n        const result = await loadTemplateFromUrl(\"https://example.com/empty-trash.json\", \"TRASH\");\n\n        expect(mockKyGet).toHaveBeenCalled();\n        expect(result).toBeNull();\n      });\n\n      test(\"should handle invalid JSON gracefully\", async () => {\n        mockKyGet.mockResolvedValue({\n          text: async () => \"invalid json {\",\n        });\n\n        const result = await loadTemplateFromUrl(\"https://example.com/invalid.json\", \"TRASH\");\n\n        expect(mockKyGet).toHaveBeenCalled();\n        expect(result).toBeNull();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "src/url-template-importer.ts",
    "content": "import ky from \"ky\";\nimport yaml from \"yaml\";\nimport { logger } from \"./logger\";\nimport { MappedTemplates } from \"./types/common.types\";\nimport { TrashQP } from \"./types/trashguide.types\";\n\nexport const isUrl = (str: string): boolean => {\n  try {\n    const url = new URL(str);\n    return url.protocol === \"http:\" || url.protocol === \"https:\";\n  } catch {\n    return false;\n  }\n};\n\nconst loadTrashTemplateFromUrl = async (url: string): Promise<TrashQP | null> => {\n  try {\n    logger.debug(`Loading TRASH template from URL: ${url}`);\n    const response = await ky.get(url, { timeout: 30000 });\n    const content = await response.text();\n    const parsed = JSON.parse(content) as TrashQP;\n\n    if (parsed == null) {\n      logger.warn(`TRASH template content from URL '${url}' is empty. Ignoring.`);\n      return null;\n    }\n\n    if (!parsed.trash_id) {\n      logger.warn(`TRASH template from URL '${url}' does not have a trash_id. Ignoring.`);\n      return null;\n    }\n\n    logger.debug(`Successfully loaded TRASH template from URL: ${url}`);\n    return parsed;\n  } catch (error) {\n    logger.error(`Failed to load TRASH template from URL '${url}': ${error instanceof Error ? error.message : String(error)}`);\n    return null;\n  }\n};\n\nconst loadRecyclarrTemplateFromUrl = async (url: string): Promise<MappedTemplates | null> => {\n  try {\n    logger.debug(`Loading template from URL: ${url}`);\n    const response = await ky.get(url, { timeout: 30000 });\n    const content = await response.text();\n    const parsed = yaml.parse(content) as MappedTemplates;\n\n    if (parsed == null) {\n      logger.warn(`Template content from URL '${url}' is empty. Ignoring.`);\n      return null;\n    }\n\n    // Transform custom formats similar to local/recyclarr templates\n    if (parsed.custom_formats) {\n      parsed.custom_formats = parsed.custom_formats.map((cf) => {\n        if (cf.assign_scores_to == null && cf.quality_profiles == null) {\n          logger.warn(`Template from URL \"${url}\" does not provide correct profile for custom format. Ignoring.`);\n        }\n\n        if (cf.quality_profiles) {\n          logger.warn(\n            `Deprecated: (Template from URL '${url}') For custom_formats please rename 'quality_profiles' to 'assign_scores_to'. See recyclarr v7.2.0`,\n          );\n        }\n        return { ...cf, assign_scores_to: cf.assign_scores_to ?? cf.quality_profiles ?? [] };\n      });\n    }\n\n    logger.debug(`Successfully loaded template from URL: ${url}`);\n    return parsed;\n  } catch (error) {\n    logger.error(`Failed to load template from URL '${url}': ${error instanceof Error ? error.message : String(error)}`);\n    return null;\n  }\n};\n\nexport const loadTemplateFromUrl = async (url: string, source?: \"TRASH\" | \"RECYCLARR\"): Promise<MappedTemplates | TrashQP | null> => {\n  if (source === \"TRASH\") {\n    return loadTrashTemplateFromUrl(url);\n  }\n  return loadRecyclarrTemplateFromUrl(url);\n};\n"
  },
  {
    "path": "src/util.test.ts",
    "content": "import path from \"path\";\nimport { describe, expect, test } from \"vitest\";\nimport { MergedCustomFormatResource } from \"./types/merged.types\";\nimport { TrashCF, TrashCFSpF } from \"./types/trashguide.types\";\nimport { cloneWithJSON, compareCustomFormats, loadJsonFile, mapImportCfToRequestCf, toCarrCF, zip } from \"./util\";\n\nconst exampleCFImplementations = {\n  name: \"TestSpec\",\n  includeCustomFormatWhenRenaming: false,\n  specifications: [\n    {\n      name: \"ReleaseTitleSpec\",\n      implementation: \"ReleaseTitleSpecification\",\n      negate: false,\n      required: false,\n      fields: {\n        value: \"expres\",\n      },\n    },\n    {\n      name: \"LanguageUnknown\",\n      implementation: \"LanguageSpecification\",\n      negate: false,\n      required: false,\n      fields: {\n        value: 0,\n      },\n    },\n    {\n      name: \"LanguageOrgi\",\n      implementation: \"LanguageSpecification\",\n      negate: false,\n      required: false,\n      fields: {\n        value: -2,\n      },\n    },\n    {\n      name: \"IndexerFlag\",\n      implementation: \"IndexerFlagSpecification\",\n      negate: false,\n      required: false,\n      fields: {\n        value: 1,\n      },\n    },\n    {\n      name: \"SourceSpec\",\n      implementation: \"SourceSpecification\",\n      negate: false,\n      required: false,\n      fields: {\n        value: 6,\n      },\n    },\n    {\n      name: \"Resolution\",\n      implementation: \"ResolutionSpecification\",\n      negate: false,\n      required: false,\n      fields: {\n        value: 540,\n      },\n    },\n    {\n      name: \"ReleaseGroup\",\n      implementation: \"ReleaseGroupSpecification\",\n      negate: false,\n      required: false,\n      fields: {\n        value: \"regex\",\n      },\n    },\n  ],\n};\n\ndescribe(\"SizeSpecification\", async () => {\n  const serverResponse: MergedCustomFormatResource = {\n    id: 103,\n    name: \"Size: Block More 40GB\",\n    includeCustomFormatWhenRenaming: false,\n    specifications: [\n      {\n        name: \"Size\",\n        implementation: \"SizeSpecification\",\n        implementationName: \"Size\",\n        infoLink: \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        negate: false,\n        required: false,\n        fields: [\n          {\n            order: 0,\n            name: \"min\",\n            label: \"Minimum Size\",\n            unit: \"GB\",\n            helpText: \"Release must be greater than this size\",\n            value: 1,\n            type: \"number\",\n            advanced: false,\n            isFloat: true,\n          },\n          {\n            order: 1,\n            name: \"max\",\n            label: \"Maximum Size\",\n            unit: \"GB\",\n            helpText: \"Release must be less than or equal to this size\",\n            value: 9,\n            type: \"number\",\n            advanced: false,\n            isFloat: true,\n          },\n        ],\n      },\n    ],\n  };\n\n  const custom: TrashCF = {\n    trash_id: \"custom-size-more-40gb\",\n    trash_scores: {\n      default: -10000,\n    },\n    trash_description: \"Size: Block sizes over 40GB\",\n    name: \"Size: Block More 40GB\",\n    includeCustomFormatWhenRenaming: false,\n    specifications: [\n      {\n        name: \"Size\",\n        implementation: \"SizeSpecification\",\n        negate: false,\n        required: false,\n        fields: {\n          min: 1,\n          max: 9,\n        },\n      },\n    ],\n  };\n\n  test(\"equal\", async () => {\n    const copied: typeof custom = JSON.parse(JSON.stringify(custom));\n\n    const result = compareCustomFormats(serverResponse, mapImportCfToRequestCf(toCarrCF(copied)));\n    expect(result.equal).toBe(true);\n  });\n\n  test(\"mismatch negate\", async () => {\n    const copied = JSON.parse(JSON.stringify(custom));\n    copied.specifications![0].negate = true;\n\n    const result = compareCustomFormats(serverResponse, mapImportCfToRequestCf(toCarrCF(copied)));\n    expect(result.equal).toBe(false);\n  });\n\n  test(\"mismatch required\", async () => {\n    const copied: typeof custom = JSON.parse(JSON.stringify(custom));\n    copied.specifications![0]!.required = true;\n\n    const result = compareCustomFormats(serverResponse, mapImportCfToRequestCf(toCarrCF(copied)));\n    expect(result.equal).toBe(false);\n  });\n\n  test(\"max differ\", async () => {\n    const copied: typeof custom = JSON.parse(JSON.stringify(custom));\n    (copied.specifications![0]!.fields as TrashCFSpF).max = 100;\n\n    const result = compareCustomFormats(serverResponse, mapImportCfToRequestCf(toCarrCF(copied)));\n    expect(result.equal).toBe(false);\n  });\n});\n\ndescribe(\"compareImportCFs - general\", async () => {\n  const filePath = path.resolve(__dirname, \"../tests/samples/20240930_cf_exceptLanguage.json\");\n  const serverResponse = loadJsonFile<MergedCustomFormatResource>(filePath);\n\n  const custom: TrashCF = {\n    trash_id: \"test123\",\n    name: \"Language: Not German or English\",\n    includeCustomFormatWhenRenaming: false,\n    specifications: [\n      {\n        name: \"Not German\",\n        implementation: \"LanguageSpecification\",\n        negate: true,\n        required: true,\n        fields: {\n          value: 4,\n        },\n      },\n    ],\n  };\n\n  test(\"should not diff for fields length bigger on remote\", async () => {\n    const copied: typeof custom = JSON.parse(JSON.stringify(custom));\n\n    const result = compareCustomFormats(serverResponse, mapImportCfToRequestCf(toCarrCF(copied)));\n    expect(result.equal).toBe(true);\n  });\n\n  test(\"should not diff for fields length equal length\", async () => {\n    const copied: typeof custom = JSON.parse(JSON.stringify(custom));\n    const clonedServer = cloneWithJSON(serverResponse);\n    clonedServer.specifications![0]!.fields = [clonedServer.specifications![0]!.fields![0]!];\n\n    expect(clonedServer.specifications![0]!.fields.length).toBe(1);\n\n    const result = compareCustomFormats(clonedServer, mapImportCfToRequestCf(toCarrCF(copied)));\n    expect(result.equal).toBe(true);\n  });\n\n  test(\"should diff for fields length if local is higher (should not happen normally)\", async () => {\n    const copied: typeof custom = JSON.parse(JSON.stringify(custom));\n    copied.specifications![0]!.fields!.exceptLanguage = false;\n\n    const clonedServer = cloneWithJSON(serverResponse);\n    clonedServer.specifications![0]!.fields = [clonedServer.specifications![0]!.fields![0]!];\n\n    expect(clonedServer.specifications![0]!.fields.length).toBe(1);\n\n    const result = compareCustomFormats(clonedServer, mapImportCfToRequestCf(toCarrCF(copied)));\n    expect(result.equal).toBe(false);\n  });\n\n  test(\"should diff for specifications length bigger on remote\", async () => {\n    const copied: typeof custom = JSON.parse(JSON.stringify(custom));\n    const clonedServer = cloneWithJSON(serverResponse);\n    clonedServer.specifications![0]!.fields = [clonedServer.specifications![0]!.fields![0]!];\n    clonedServer.specifications?.push(clonedServer.specifications![0]!);\n\n    expect(clonedServer.specifications![0]!.fields.length).toBe(1);\n\n    const result = compareCustomFormats(clonedServer, mapImportCfToRequestCf(toCarrCF(copied)));\n    expect(result.equal).toBe(false);\n  });\n\n  test(\"should diff for specifications length smaller on remote\", async () => {\n    const copied: typeof custom = JSON.parse(JSON.stringify(custom));\n    const clonedServer = cloneWithJSON(serverResponse);\n    clonedServer.specifications![0]!.fields = [clonedServer.specifications![0]!.fields![0]!];\n    copied.specifications?.push(copied.specifications[0]!);\n\n    expect(clonedServer.specifications![0]!.fields.length).toBe(1);\n\n    const result = compareCustomFormats(clonedServer, mapImportCfToRequestCf(toCarrCF(copied)));\n    expect(result.equal).toBe(false);\n  });\n\n  test(\"should not diff for specifications length equal\", async () => {\n    const copied: typeof custom = JSON.parse(JSON.stringify(custom));\n    const clonedServer = cloneWithJSON(serverResponse);\n    clonedServer.specifications![0]!.fields = [clonedServer.specifications![0]!.fields![0]!];\n\n    expect(clonedServer.specifications![0]!.fields.length).toBe(1);\n\n    const result = compareCustomFormats(clonedServer, mapImportCfToRequestCf(toCarrCF(copied)));\n    expect(result.equal).toBe(true);\n  });\n});\n\ndescribe(\"zip function\", async () => {\n  test(\"should work for empty inputs\", async () => {\n    expect(zip([], [])).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "src/util.ts",
    "content": "import { existsSync, mkdirSync, readFileSync, rmSync } from \"node:fs\";\nimport path from \"node:path\";\nimport simpleGit, { CheckRepoActions } from \"simple-git\";\nimport { MergedCustomFormatResource } from \"./types/merged.types\";\nimport { getHelpers } from \"./env\";\nimport { logger } from \"./logger\";\nimport { ConfigarrCF, ImportCF, UserFriendlyField } from \"./types/common.types\";\nimport { TrashCF } from \"./types/trashguide.types\";\n\nconst recyclarrConfigPath = `${getHelpers().repoPath}/recyclarr-config`;\nconst recyclarrSonarrRoot = `${recyclarrConfigPath}/sonarr`;\nconst recyclarrSonarrCFs = `${recyclarrSonarrRoot}/includes/custom-formats`;\nconst recyclarrSonarrQDs = `${recyclarrSonarrRoot}/includes/quality-definitions`;\nconst recyclarrSonarrQPs = `${recyclarrSonarrRoot}/includes/quality-profiles`;\n\nconst recyclarrRadarrRoot = `${recyclarrConfigPath}/radarr`;\nconst recyclarrRadarrCFs = `${recyclarrRadarrRoot}/includes/custom-formats`;\nconst recyclarrRadarrQDs = `${recyclarrRadarrRoot}/includes/quality-definitions`;\nconst recyclarrRadarrQPs = `${recyclarrRadarrRoot}/includes/quality-profiles`;\n\nconst trashRepoRoot = `${getHelpers().repoPath}/trash-guides`;\nconst trashRepoPath = \"docs/json\";\nconst trashRepoSonarrRoot = `${trashRepoRoot}/${trashRepoPath}/sonarr`;\nconst trashRepoRadarrRoot = `${trashRepoRoot}/${trashRepoPath}/radarr`;\n\nexport const trashRepoPaths = {\n  root: trashRepoRoot,\n  sonarrCF: `${trashRepoSonarrRoot}/cf`,\n  sonarrCFGroups: `${trashRepoSonarrRoot}/cf-groups`,\n  sonarrQualitySize: `${trashRepoSonarrRoot}/quality-size`,\n  sonarrQP: `${trashRepoSonarrRoot}/quality-profiles`,\n  sonarrNaming: `${trashRepoSonarrRoot}/naming`,\n  sonarrConflicts: `${trashRepoSonarrRoot}/conflicts.json`,\n  radarrCF: `${trashRepoRadarrRoot}/cf`,\n  radarrCFGroups: `${trashRepoRadarrRoot}/cf-groups`,\n  radarrQualitySize: `${trashRepoRadarrRoot}/quality-size`,\n  radarrQP: `${trashRepoRadarrRoot}/quality-profiles`,\n  radarrNaming: `${trashRepoRadarrRoot}/naming`,\n  radarrConflicts: `${trashRepoRadarrRoot}/conflicts.json`,\n};\n\nexport const recyclarrRepoPaths = {\n  root: recyclarrConfigPath,\n  sonarrCF: `${recyclarrSonarrCFs}`,\n  sonarrQD: `${recyclarrSonarrQDs}`,\n  sonarrQP: `${recyclarrSonarrQPs}`,\n  radarrCF: `${recyclarrRadarrCFs}`,\n  radarrQD: `${recyclarrRadarrQDs}`,\n  radarrQP: `${recyclarrRadarrQPs}`,\n};\n\nexport const trashToCarrCF = ({ trash_id, trash_regex, trash_scores, ...rest }: TrashCF): ConfigarrCF => {\n  return {\n    ...rest,\n    configarr_id: trash_id,\n    configarr_scores: trash_scores,\n  };\n};\n\nexport const toCarrCF = (input: TrashCF | ConfigarrCF): ConfigarrCF => {\n  if (\"configarr_id\" in input) {\n    return input;\n  }\n\n  return trashToCarrCF(input);\n};\n\nexport const mapImportCfToRequestCf = (cf: TrashCF | ConfigarrCF): MergedCustomFormatResource => {\n  let customId;\n  let rest: ImportCF;\n\n  if (\"trash_id\" in cf) {\n    customId = cf.trash_id;\n    const { trash_id, trash_scores, ...restCf } = cf;\n    rest = restCf;\n  } else {\n    customId = cf.configarr_id;\n    const { configarr_id, configarr_scores, ...restCf } = cf;\n    rest = restCf;\n  }\n\n  if (!rest.specifications) {\n    logger.info(`ImportCF is wrong ${customId}, ${cf.name}.`);\n    throw new Error(\"ImportCF wrong\");\n  }\n\n  const specs = rest.specifications.map((spec) => {\n    const newFields: UserFriendlyField[] = [];\n\n    if (!spec.fields) {\n      logger.info(`Spec: ${spec.name} fields is not defined`);\n      throw new Error(`Spec is not correctly defined: ${spec.name}`);\n    }\n\n    // 2024-09-30: Test if this handles all cases\n    newFields.push(...Object.entries(spec.fields).map(([key, value]) => ({ name: key, value })));\n\n    return {\n      ...spec,\n      fields: newFields.map((f) => {\n        if (f.name) {\n          return f;\n        }\n\n        return { ...f, name: \"value\" };\n      }),\n    };\n  });\n\n  return { ...rest, specifications: specs };\n};\n\nexport function compareCustomFormats(\n  serverObject: MergedCustomFormatResource,\n  localObject: MergedCustomFormatResource,\n): ReturnType<typeof compareObjectsCarr> {\n  return compareObjectsCarr(serverObject, localObject);\n}\n\nexport function compareNaming(serverObject: any, localObject: any): ReturnType<typeof compareObjectsCarr> {\n  return compareObjectsCarr(serverObject, localObject);\n}\n\nexport function compareMediamanagement(serverObject: any, localObject: any): ReturnType<typeof compareObjectsCarr> {\n  return compareObjectsCarr(serverObject, localObject);\n}\n\nexport function compareObjectsCarr(serverObject: any, localObject: any, parent?: string): { equal: boolean; changes: string[] } {\n  const changes: string[] = [];\n\n  for (const key in localObject) {\n    if (Object.prototype.hasOwnProperty.call(localObject, key)) {\n      if (Object.prototype.hasOwnProperty.call(serverObject, key)) {\n        const serverProperty = serverObject[key];\n        let localProperty = localObject[key];\n\n        if (Array.isArray(serverProperty)) {\n          if (!Array.isArray(localProperty)) {\n            changes.push(`Expected array for key ${key} in localProperty`);\n            continue;\n          }\n\n          let arrayLengthMismatch = false;\n\n          if (key === \"fields\") {\n            // Only if server does provide less props as we have -> assume change required.\n            // For example if radarr introduces new fields for custom formats and we do not have them included this would result in always changed results.\n            arrayLengthMismatch = serverProperty.length < localProperty.length;\n          } else if (serverProperty.length != localProperty.length) {\n            arrayLengthMismatch = true;\n          }\n\n          if (arrayLengthMismatch) {\n            changes.push(\n              `Array length mismatch for key ${key} (parent: ${parent}): serverProperty length ${serverProperty.length}, localProperty length ${localProperty.length}`,\n            );\n            continue;\n          }\n\n          for (let i = 0; i < serverProperty.length; i++) {\n            const { equal: isEqual, changes: subChanges } = compareObjectsCarr(serverProperty[i], localProperty[i], key);\n            // changes.push(\n            //   ...subChanges.map((subChange) => `${key}[${i}].${subChange}`)\n            // );\n\n            if (subChanges.length > 0) {\n              changes.push(`${key}[${i}].${subChanges[0]}`);\n            }\n\n            if (!isEqual && changes.length <= 0) {\n              changes.push(`Mismatch found in array element at index ${i} for key '${key}'`);\n            }\n          }\n        } else if (typeof localProperty === \"object\" && localProperty !== null) {\n          if (typeof serverProperty !== \"object\" || serverProperty === null) {\n            changes.push(`Expected object for key '${key}' in serverProperty`);\n            continue;\n          }\n\n          const { equal: isEqual, changes: subChanges } = compareObjectsCarr(serverProperty, localProperty, key);\n          changes.push(...subChanges.map((subChange) => `${key}.${subChange}`));\n          if (!isEqual) {\n            changes.push(`Mismatch found for key '${key}'`);\n          }\n        } else {\n          if (serverProperty !== localProperty) {\n            changes.push(`Mismatch found for key '${key}': server value '${serverProperty}', value to set '${localProperty}'`);\n          }\n        }\n      } else {\n        logger.debug(`Ignore unknown key '${key}' for comparison.`);\n      }\n    }\n  }\n\n  const equal = changes.length === 0;\n  return { equal, changes };\n}\n\nexport function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {\n  if (value === null || value === undefined) return false;\n  const testDummy: TValue = value;\n  return true;\n}\n\nexport const cloneWithJSON = <T>(input: T): T => {\n  return JSON.parse(JSON.stringify(input));\n};\n\nexport const ROOT_PATH = path.resolve(process.cwd());\n\nexport const loadJsonFile = <T = object>(filePath: string) => {\n  const file = readFileSync(filePath, { encoding: \"utf-8\" });\n  return JSON.parse(file) as T;\n};\n\nexport function zip<T extends unknown[][]>(...arrays: T): Array<{ [K in keyof T]: T[K] extends (infer U)[] ? U : never }> {\n  let length = -1;\n  let mismatch = false;\n\n  for (const arrayElement of arrays) {\n    if (length <= 0) {\n      length = arrayElement.length;\n    } else {\n      mismatch = length !== arrayElement.length;\n    }\n  }\n\n  if (mismatch) {\n    throw new Error(\"Zip error with not equal lengths\");\n  }\n\n  const result = [];\n\n  for (let i = 0; i < length; i++) {\n    result.push(arrays.map((arr) => arr[i]));\n  }\n\n  return result as Array<{ [K in keyof T]: T[K] extends (infer U)[] ? U : never }>;\n}\n\nexport function zipNLength<T extends unknown[][]>(...arrays: T): Array<{ [K in keyof T]: T[K] extends (infer U)[] ? U : never }> {\n  const minLength = Math.min(...arrays.map((arr) => arr.length));\n  const result = [];\n\n  for (let i = 0; i < minLength; i++) {\n    result.push(arrays.map((arr) => arr[i]));\n  }\n\n  return result as Array<{ [K in keyof T]: T[K] extends (infer U)[] ? U : never }>;\n}\n\nasync function performCloneOperation(gitUrl: string, rootPath: string, sparseConfig: { disabled: boolean; sparseDirs?: string[] }) {\n  const gitClient = simpleGit({ baseDir: rootPath });\n  const disableGitCloneOptions = process.env.CONFIGARR_DISABLE_GIT_CLONE_OPTIONS !== undefined;\n  let sparseEnabled = false;\n\n  if (!sparseConfig.disabled && sparseConfig.sparseDirs && sparseConfig.sparseDirs.length > 0) {\n    sparseEnabled = true;\n    logger.debug(`Sparse checkout enabled for directories: ${sparseConfig.sparseDirs.join(\", \")}`);\n  } else {\n    logger.debug(`Sparse checkout disabled`);\n  }\n\n  if (disableGitCloneOptions) {\n    logger.debug(`CONFIGARR_DISABLE_GIT_CLONE_OPTIONS is set - using clean clone without filter or sparse options`);\n    // Clean clone without filter or sparse options\n    await simpleGit().clone(gitUrl, rootPath);\n  } else {\n    const cloneArgs = [\"--filter=blob:none\"];\n    if (sparseEnabled) {\n      cloneArgs.push(\"--sparse\");\n    }\n    await simpleGit().clone(gitUrl, rootPath, cloneArgs);\n  }\n\n  if (sparseEnabled && !disableGitCloneOptions) {\n    await gitClient.raw([\"sparse-checkout\", \"set\", ...sparseConfig.sparseDirs!]);\n  }\n\n  logger.info(`Cloned repository: '${gitUrl}'`);\n}\n\nexport const cloneGitRepo = async (\n  localPath: string,\n  gitUrl: string,\n  revision: string,\n  cloneConf: {\n    disabled: boolean;\n    sparseDirs?: string[];\n  },\n) => {\n  const rootPath = localPath;\n\n  if (!existsSync(rootPath)) {\n    mkdirSync(rootPath, { recursive: true });\n  }\n\n  let gitClient = simpleGit({ baseDir: rootPath });\n  const isRepo = await gitClient.checkIsRepo(CheckRepoActions.IS_REPO_ROOT);\n\n  // Check if we need to re-clone due to remote URL change\n  let shouldReClone = false;\n  if (isRepo) {\n    try {\n      const remotes = await gitClient.getRemotes(true);\n      const originRemote = remotes.find((remote) => remote.name === \"origin\");\n\n      if (originRemote && originRemote.refs.fetch !== gitUrl) {\n        logger.info(`Remote URL for repository has changed from '${originRemote.refs.fetch}' to '${gitUrl}'`);\n        shouldReClone = true;\n      }\n    } catch (err: unknown) {\n      const errorMsg = err instanceof Error ? err.message : String(err);\n      logger.debug(`Failed to check remote URL: ${errorMsg}`);\n      // If we can't determine the URL, treat as needing re-clone to be safe\n      shouldReClone = true;\n    }\n  }\n\n  // Delete and re-clone if repository doesn't exist or URL changed\n  if (!isRepo || shouldReClone) {\n    if (shouldReClone) {\n      logger.info(`Removing existing repository at '${rootPath}' and re-cloning...`);\n      rmSync(rootPath, { recursive: true, force: true });\n      mkdirSync(rootPath, { recursive: true });\n    }\n\n    await performCloneOperation(gitUrl, rootPath, cloneConf);\n  }\n\n  // Re-instantiate gitClient after potential re-clone\n  // (if we re-cloned, the old client references a deleted directory)\n  gitClient = simpleGit({ baseDir: rootPath });\n\n  // Checkout the specified revision\n  try {\n    await gitClient.checkout(revision, [\"-f\"]);\n    const result = await gitClient.status();\n\n    let updated = false;\n\n    // Pull if not in detached HEAD state\n    if (!result.detached) {\n      const res = await gitClient.pull();\n      if (res.files.length > 0) {\n        updated = true;\n        logger.info(`Repository updated to new commit: '${gitUrl}' at '${revision}'`);\n      }\n    }\n\n    let hash: string = \"unknown\";\n\n    try {\n      hash = await gitClient.revparse([\"--verify\", \"HEAD\"]);\n    } catch (err: unknown) {\n      // Ignore\n      logger.debug(`Unable to extract hash from commit`);\n    }\n\n    return {\n      ref: result.current,\n      hash: hash,\n      localPath: localPath,\n      updated,\n    };\n  } catch (err: unknown) {\n    const errorMsg = err instanceof Error ? err.message : String(err);\n    logger.error(`Failed to checkout revision '${revision}' from '${gitUrl}': ${errorMsg}`);\n    throw new Error(\n      `Unable to checkout revision '${revision}' from '${gitUrl}'. The revision may not exist in the repository. Error: ${errorMsg}`,\n    );\n  }\n};\n\nexport const roundToDecimal = (num: number, decimalPlaces = 0) => {\n  const p = Math.pow(10, decimalPlaces);\n  return Math.round((num + Number.EPSILON) * p) / p;\n};\n\nexport function pickFromConst<T extends readonly string[], K extends T[number]>(constArray: T, keys: readonly K[]): readonly K[] {\n  return keys.filter((key): key is K => constArray.includes(key));\n}\n\nexport function isInConstArray<T extends readonly unknown[]>(array: T, value: unknown): value is T[number] {\n  return array.includes(value as T[number]);\n}\n\n/**\n * Convert snake_case string to camelCase\n * @param str - The snake_case string to convert\n * @returns The camelCase version of the string\n */\nexport const snakeToCamel = (str: string): string => {\n  return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n};\n\n/**\n * Convert camelCase to snake_case\n */\nexport const camelToSnake = (str: string): string => {\n  return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n};\n"
  },
  {
    "path": "tests/e2e/demo-todo-app.spec.ts",
    "content": "import { expect, test, type Page } from \"@playwright/test\";\n\nconst TODO_ITEMS = [\"buy some cheese\", \"feed the cat\", \"book a doctors appointment\"] as const;\n\ntest.beforeEach(async ({ page }) => {\n  await page.goto(\"https://demo.playwright.dev/todomvc\");\n});\n\ntest.describe(\"New Todo\", () => {\n  test(\"should allow me to add todo items\", async ({ page }) => {\n    // create a new todo locator\n    const newTodo = page.getByPlaceholder(\"What needs to be done?\");\n\n    // Create 1st todo.\n    await newTodo.fill(TODO_ITEMS[0]);\n    await newTodo.press(\"Enter\");\n\n    // Make sure the list only has one todo item.\n    await expect(page.getByTestId(\"todo-title\")).toHaveText([TODO_ITEMS[0]]);\n\n    // Create 2nd todo.\n    await newTodo.fill(TODO_ITEMS[1]);\n    await newTodo.press(\"Enter\");\n\n    // Make sure the list now has two todo items.\n    await expect(page.getByTestId(\"todo-title\")).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);\n\n    await checkNumberOfTodosInLocalStorage(page, 2);\n  });\n\n  test(\"should clear text input field when an item is added\", async ({ page }) => {\n    // create a new todo locator\n    const newTodo = page.getByPlaceholder(\"What needs to be done?\");\n\n    // Create one todo item.\n    await newTodo.fill(TODO_ITEMS[0]);\n    await newTodo.press(\"Enter\");\n\n    // Check that input is empty.\n    await expect(newTodo).toBeEmpty();\n    await checkNumberOfTodosInLocalStorage(page, 1);\n  });\n\n  test(\"should append new items to the bottom of the list\", async ({ page }) => {\n    // Create 3 items.\n    await createDefaultTodos(page);\n\n    // create a todo count locator\n    const todoCount = page.getByTestId(\"todo-count\");\n\n    // Check test using different methods.\n    await expect(page.getByText(\"3 items left\")).toBeVisible();\n    await expect(todoCount).toHaveText(\"3 items left\");\n    await expect(todoCount).toContainText(\"3\");\n    await expect(todoCount).toHaveText(/3/);\n\n    // Check all items in one call.\n    await expect(page.getByTestId(\"todo-title\")).toHaveText(TODO_ITEMS);\n    await checkNumberOfTodosInLocalStorage(page, 3);\n  });\n});\n\ntest.describe(\"Mark all as completed\", () => {\n  test.beforeEach(async ({ page }) => {\n    await createDefaultTodos(page);\n    await checkNumberOfTodosInLocalStorage(page, 3);\n  });\n\n  test.afterEach(async ({ page }) => {\n    await checkNumberOfTodosInLocalStorage(page, 3);\n  });\n\n  test(\"should allow me to mark all items as completed\", async ({ page }) => {\n    // Complete all todos.\n    await page.getByLabel(\"Mark all as complete\").check();\n\n    // Ensure all todos have 'completed' class.\n    await expect(page.getByTestId(\"todo-item\")).toHaveClass([\"completed\", \"completed\", \"completed\"]);\n    await checkNumberOfCompletedTodosInLocalStorage(page, 3);\n  });\n\n  test(\"should allow me to clear the complete state of all items\", async ({ page }) => {\n    const toggleAll = page.getByLabel(\"Mark all as complete\");\n    // Check and then immediately uncheck.\n    await toggleAll.check();\n    await toggleAll.uncheck();\n\n    // Should be no completed classes.\n    await expect(page.getByTestId(\"todo-item\")).toHaveClass([\"\", \"\", \"\"]);\n  });\n\n  test(\"complete all checkbox should update state when items are completed / cleared\", async ({ page }) => {\n    const toggleAll = page.getByLabel(\"Mark all as complete\");\n    await toggleAll.check();\n    await expect(toggleAll).toBeChecked();\n    await checkNumberOfCompletedTodosInLocalStorage(page, 3);\n\n    // Uncheck first todo.\n    const firstTodo = page.getByTestId(\"todo-item\").nth(0);\n    await firstTodo.getByRole(\"checkbox\").uncheck();\n\n    // Reuse toggleAll locator and make sure its not checked.\n    await expect(toggleAll).not.toBeChecked();\n\n    await firstTodo.getByRole(\"checkbox\").check();\n    await checkNumberOfCompletedTodosInLocalStorage(page, 3);\n\n    // Assert the toggle all is checked again.\n    await expect(toggleAll).toBeChecked();\n  });\n});\n\ntest.describe(\"Item\", () => {\n  test(\"should allow me to mark items as complete\", async ({ page }) => {\n    // create a new todo locator\n    const newTodo = page.getByPlaceholder(\"What needs to be done?\");\n\n    // Create two items.\n    for (const item of TODO_ITEMS.slice(0, 2)) {\n      await newTodo.fill(item);\n      await newTodo.press(\"Enter\");\n    }\n\n    // Check first item.\n    const firstTodo = page.getByTestId(\"todo-item\").nth(0);\n    await firstTodo.getByRole(\"checkbox\").check();\n    await expect(firstTodo).toHaveClass(\"completed\");\n\n    // Check second item.\n    const secondTodo = page.getByTestId(\"todo-item\").nth(1);\n    await expect(secondTodo).not.toHaveClass(\"completed\");\n    await secondTodo.getByRole(\"checkbox\").check();\n\n    // Assert completed class.\n    await expect(firstTodo).toHaveClass(\"completed\");\n    await expect(secondTodo).toHaveClass(\"completed\");\n  });\n\n  test(\"should allow me to un-mark items as complete\", async ({ page }) => {\n    // create a new todo locator\n    const newTodo = page.getByPlaceholder(\"What needs to be done?\");\n\n    // Create two items.\n    for (const item of TODO_ITEMS.slice(0, 2)) {\n      await newTodo.fill(item);\n      await newTodo.press(\"Enter\");\n    }\n\n    const firstTodo = page.getByTestId(\"todo-item\").nth(0);\n    const secondTodo = page.getByTestId(\"todo-item\").nth(1);\n    const firstTodoCheckbox = firstTodo.getByRole(\"checkbox\");\n\n    await firstTodoCheckbox.check();\n    await expect(firstTodo).toHaveClass(\"completed\");\n    await expect(secondTodo).not.toHaveClass(\"completed\");\n    await checkNumberOfCompletedTodosInLocalStorage(page, 1);\n\n    await firstTodoCheckbox.uncheck();\n    await expect(firstTodo).not.toHaveClass(\"completed\");\n    await expect(secondTodo).not.toHaveClass(\"completed\");\n    await checkNumberOfCompletedTodosInLocalStorage(page, 0);\n  });\n\n  test(\"should allow me to edit an item\", async ({ page }) => {\n    await createDefaultTodos(page);\n\n    const todoItems = page.getByTestId(\"todo-item\");\n    const secondTodo = todoItems.nth(1);\n    await secondTodo.dblclick();\n    await expect(secondTodo.getByRole(\"textbox\", { name: \"Edit\" })).toHaveValue(TODO_ITEMS[1]);\n    await secondTodo.getByRole(\"textbox\", { name: \"Edit\" }).fill(\"buy some sausages\");\n    await secondTodo.getByRole(\"textbox\", { name: \"Edit\" }).press(\"Enter\");\n\n    // Explicitly assert the new text value.\n    await expect(todoItems).toHaveText([TODO_ITEMS[0], \"buy some sausages\", TODO_ITEMS[2]]);\n    await checkTodosInLocalStorage(page, \"buy some sausages\");\n  });\n});\n\ntest.describe(\"Editing\", () => {\n  test.beforeEach(async ({ page }) => {\n    await createDefaultTodos(page);\n    await checkNumberOfTodosInLocalStorage(page, 3);\n  });\n\n  test(\"should hide other controls when editing\", async ({ page }) => {\n    const todoItem = page.getByTestId(\"todo-item\").nth(1);\n    await todoItem.dblclick();\n    await expect(todoItem.getByRole(\"checkbox\")).not.toBeVisible();\n    await expect(\n      todoItem.locator(\"label\", {\n        hasText: TODO_ITEMS[1],\n      }),\n    ).not.toBeVisible();\n    await checkNumberOfTodosInLocalStorage(page, 3);\n  });\n\n  test(\"should save edits on blur\", async ({ page }) => {\n    const todoItems = page.getByTestId(\"todo-item\");\n    await todoItems.nth(1).dblclick();\n    await todoItems.nth(1).getByRole(\"textbox\", { name: \"Edit\" }).fill(\"buy some sausages\");\n    await todoItems.nth(1).getByRole(\"textbox\", { name: \"Edit\" }).dispatchEvent(\"blur\");\n\n    await expect(todoItems).toHaveText([TODO_ITEMS[0], \"buy some sausages\", TODO_ITEMS[2]]);\n    await checkTodosInLocalStorage(page, \"buy some sausages\");\n  });\n\n  test(\"should trim entered text\", async ({ page }) => {\n    const todoItems = page.getByTestId(\"todo-item\");\n    await todoItems.nth(1).dblclick();\n    await todoItems.nth(1).getByRole(\"textbox\", { name: \"Edit\" }).fill(\"    buy some sausages    \");\n    await todoItems.nth(1).getByRole(\"textbox\", { name: \"Edit\" }).press(\"Enter\");\n\n    await expect(todoItems).toHaveText([TODO_ITEMS[0], \"buy some sausages\", TODO_ITEMS[2]]);\n    await checkTodosInLocalStorage(page, \"buy some sausages\");\n  });\n\n  test(\"should remove the item if an empty text string was entered\", async ({ page }) => {\n    const todoItems = page.getByTestId(\"todo-item\");\n    await todoItems.nth(1).dblclick();\n    await todoItems.nth(1).getByRole(\"textbox\", { name: \"Edit\" }).fill(\"\");\n    await todoItems.nth(1).getByRole(\"textbox\", { name: \"Edit\" }).press(\"Enter\");\n\n    await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);\n  });\n\n  test(\"should cancel edits on escape\", async ({ page }) => {\n    const todoItems = page.getByTestId(\"todo-item\");\n    await todoItems.nth(1).dblclick();\n    await todoItems.nth(1).getByRole(\"textbox\", { name: \"Edit\" }).fill(\"buy some sausages\");\n    await todoItems.nth(1).getByRole(\"textbox\", { name: \"Edit\" }).press(\"Escape\");\n    await expect(todoItems).toHaveText(TODO_ITEMS);\n  });\n});\n\ntest.describe(\"Counter\", () => {\n  test(\"should display the current number of todo items\", async ({ page }) => {\n    // create a new todo locator\n    const newTodo = page.getByPlaceholder(\"What needs to be done?\");\n\n    // create a todo count locator\n    const todoCount = page.getByTestId(\"todo-count\");\n\n    await newTodo.fill(TODO_ITEMS[0]);\n    await newTodo.press(\"Enter\");\n\n    await expect(todoCount).toContainText(\"1\");\n\n    await newTodo.fill(TODO_ITEMS[1]);\n    await newTodo.press(\"Enter\");\n    await expect(todoCount).toContainText(\"2\");\n\n    await checkNumberOfTodosInLocalStorage(page, 2);\n  });\n});\n\ntest.describe(\"Clear completed button\", () => {\n  test.beforeEach(async ({ page }) => {\n    await createDefaultTodos(page);\n  });\n\n  test(\"should display the correct text\", async ({ page }) => {\n    await page.locator(\".todo-list li .toggle\").first().check();\n    await expect(page.getByRole(\"button\", { name: \"Clear completed\" })).toBeVisible();\n  });\n\n  test(\"should remove completed items when clicked\", async ({ page }) => {\n    const todoItems = page.getByTestId(\"todo-item\");\n    await todoItems.nth(1).getByRole(\"checkbox\").check();\n    await page.getByRole(\"button\", { name: \"Clear completed\" }).click();\n    await expect(todoItems).toHaveCount(2);\n    await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);\n  });\n\n  test(\"should be hidden when there are no items that are completed\", async ({ page }) => {\n    await page.locator(\".todo-list li .toggle\").first().check();\n    await page.getByRole(\"button\", { name: \"Clear completed\" }).click();\n    await expect(page.getByRole(\"button\", { name: \"Clear completed\" })).toBeHidden();\n  });\n});\n\ntest.describe(\"Persistence\", () => {\n  test(\"should persist its data\", async ({ page }) => {\n    // create a new todo locator\n    const newTodo = page.getByPlaceholder(\"What needs to be done?\");\n\n    for (const item of TODO_ITEMS.slice(0, 2)) {\n      await newTodo.fill(item);\n      await newTodo.press(\"Enter\");\n    }\n\n    const todoItems = page.getByTestId(\"todo-item\");\n    const firstTodoCheck = todoItems.nth(0).getByRole(\"checkbox\");\n    await firstTodoCheck.check();\n    await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);\n    await expect(firstTodoCheck).toBeChecked();\n    await expect(todoItems).toHaveClass([\"completed\", \"\"]);\n\n    // Ensure there is 1 completed item.\n    await checkNumberOfCompletedTodosInLocalStorage(page, 1);\n\n    // Now reload.\n    await page.reload();\n    await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);\n    await expect(firstTodoCheck).toBeChecked();\n    await expect(todoItems).toHaveClass([\"completed\", \"\"]);\n  });\n});\n\ntest.describe(\"Routing\", () => {\n  test.beforeEach(async ({ page }) => {\n    await createDefaultTodos(page);\n    // make sure the app had a chance to save updated todos in storage\n    // before navigating to a new view, otherwise the items can get lost :(\n    // in some frameworks like Durandal\n    await checkTodosInLocalStorage(page, TODO_ITEMS[0]);\n  });\n\n  test(\"should allow me to display active items\", async ({ page }) => {\n    const todoItem = page.getByTestId(\"todo-item\");\n    await page.getByTestId(\"todo-item\").nth(1).getByRole(\"checkbox\").check();\n\n    await checkNumberOfCompletedTodosInLocalStorage(page, 1);\n    await page.getByRole(\"link\", { name: \"Active\" }).click();\n    await expect(todoItem).toHaveCount(2);\n    await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);\n  });\n\n  test(\"should respect the back button\", async ({ page }) => {\n    const todoItem = page.getByTestId(\"todo-item\");\n    await page.getByTestId(\"todo-item\").nth(1).getByRole(\"checkbox\").check();\n\n    await checkNumberOfCompletedTodosInLocalStorage(page, 1);\n\n    await test.step(\"Showing all items\", async () => {\n      await page.getByRole(\"link\", { name: \"All\" }).click();\n      await expect(todoItem).toHaveCount(3);\n    });\n\n    await test.step(\"Showing active items\", async () => {\n      await page.getByRole(\"link\", { name: \"Active\" }).click();\n    });\n\n    await test.step(\"Showing completed items\", async () => {\n      await page.getByRole(\"link\", { name: \"Completed\" }).click();\n    });\n\n    await expect(todoItem).toHaveCount(1);\n    await page.goBack();\n    await expect(todoItem).toHaveCount(2);\n    await page.goBack();\n    await expect(todoItem).toHaveCount(3);\n  });\n\n  test(\"should allow me to display completed items\", async ({ page }) => {\n    await page.getByTestId(\"todo-item\").nth(1).getByRole(\"checkbox\").check();\n    await checkNumberOfCompletedTodosInLocalStorage(page, 1);\n    await page.getByRole(\"link\", { name: \"Completed\" }).click();\n    await expect(page.getByTestId(\"todo-item\")).toHaveCount(1);\n  });\n\n  test(\"should allow me to display all items\", async ({ page }) => {\n    await page.getByTestId(\"todo-item\").nth(1).getByRole(\"checkbox\").check();\n    await checkNumberOfCompletedTodosInLocalStorage(page, 1);\n    await page.getByRole(\"link\", { name: \"Active\" }).click();\n    await page.getByRole(\"link\", { name: \"Completed\" }).click();\n    await page.getByRole(\"link\", { name: \"All\" }).click();\n    await expect(page.getByTestId(\"todo-item\")).toHaveCount(3);\n  });\n\n  test(\"should highlight the currently applied filter\", async ({ page }) => {\n    await expect(page.getByRole(\"link\", { name: \"All\" })).toHaveClass(\"selected\");\n\n    //create locators for active and completed links\n    const activeLink = page.getByRole(\"link\", { name: \"Active\" });\n    const completedLink = page.getByRole(\"link\", { name: \"Completed\" });\n    await activeLink.click();\n\n    // Page change - active items.\n    await expect(activeLink).toHaveClass(\"selected\");\n    await completedLink.click();\n\n    // Page change - completed items.\n    await expect(completedLink).toHaveClass(\"selected\");\n  });\n});\n\nasync function createDefaultTodos(page: Page) {\n  // create a new todo locator\n  const newTodo = page.getByPlaceholder(\"What needs to be done?\");\n\n  for (const item of TODO_ITEMS) {\n    await newTodo.fill(item);\n    await newTodo.press(\"Enter\");\n  }\n}\n\nasync function checkNumberOfTodosInLocalStorage(page: Page, expected: number) {\n  return await page.waitForFunction((e) => {\n    return JSON.parse(localStorage[\"react-todos\"]).length === e;\n  }, expected);\n}\n\nasync function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) {\n  return await page.waitForFunction((e) => {\n    return JSON.parse(localStorage[\"react-todos\"]).filter((todo: any) => todo.completed).length === e;\n  }, expected);\n}\n\nasync function checkTodosInLocalStorage(page: Page, title: string) {\n  return await page.waitForFunction((t) => {\n    return JSON.parse(localStorage[\"react-todos\"])\n      .map((todo: any) => todo.title)\n      .includes(t);\n  }, title);\n}\n"
  },
  {
    "path": "tests/samples/20240930_cf_exceptLanguage.json",
    "content": "{\n  \"id\": 98,\n  \"name\": \"Language: Not German or English\",\n  \"includeCustomFormatWhenRenaming\": false,\n  \"specifications\": [\n    {\n      \"name\": \"Not German\",\n      \"implementation\": \"LanguageSpecification\",\n      \"implementationName\": \"Language\",\n      \"infoLink\": \"https://wiki.servarr.com/radarr/settings#custom-formats-2\",\n      \"negate\": true,\n      \"required\": true,\n      \"fields\": [\n        {\n          \"order\": 0,\n          \"name\": \"value\",\n          \"label\": \"Language\",\n          \"value\": 4,\n          \"type\": \"select\",\n          \"advanced\": false,\n          \"selectOptions\": [\n            {\n              \"value\": -1,\n              \"name\": \"Any\",\n              \"order\": 0,\n              \"dividerAfter\": false\n            }\n          ],\n          \"privacy\": \"normal\",\n          \"isFloat\": false\n        },\n        {\n          \"order\": 1,\n          \"name\": \"exceptLanguage\",\n          \"label\": \"Except Language\",\n          \"helpText\": \"Matches if any language other than the selected language is present\",\n          \"value\": false,\n          \"type\": \"checkbox\",\n          \"advanced\": false,\n          \"privacy\": \"normal\",\n          \"isFloat\": false\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "tests/samples/cfs.json",
    "content": "[\n  {\n    \"id\": 1,\n    \"name\": \"BR-DISK\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"BR-DISK\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(?!.*\\\\b((?<!HD[._ -]|HD)DVD|BDRip|720p|MKV|XviD|WMV|d3g|(BD)?REMUX|^(?=.*1080p)(?=.*HEVC)|[xh][-_. ]?26[45]|German.*[DM]L|((?<=\\\\d{4}).*German.*([DM]L)?)(?=.*\\\\b(AVC|HEVC|VC[-_. ]?1|MVC|MPEG[-_. ]?2)\\\\b))\\\\b)(((?=.*\\\\b(Blu[-_. ]?ray|BD|HD[-_. ]?DVD)\\\\b)(?=.*\\\\b(AVC|HEVC|VC[-_. ]?1|MVC|MPEG[-_. ]?2|BDMV|ISO)\\\\b))|^((?=.*\\\\b(((?=.*\\\\b((.*_)?COMPLETE.*|Dis[ck])\\\\b)(?=.*(Blu[-_. ]?ray|HD[-_. ]?DVD)))|3D[-_. ]?BD|BR[-_. ]?DISK|Full[-_. ]?Blu[-_. ]?ray|^((?=.*((BD|UHD)[-_. ]?(25|50|66|100|ISO)))))))).*\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 2,\n    \"name\": \"LQ\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"BRiNK\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(BRiNK)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CHX\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(CHX)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CTFOH\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(CTFOH)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"d3g\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(d3g)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"EVO\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(EVO)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"FGT\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(FGT)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"GHOSTS\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(GHOSTS)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HiQVE\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(HiQVE)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"iNTENSO\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(iNTENSO)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"JFF\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(JFF)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MeGusta\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(MeGusta)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NERO\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(NERO)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"nhanc3\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(nhanc3)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Pahe\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Pahe(\\\\.(ph|in))?\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"PSA\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(PSA)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TBS\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(TBS)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TG\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(TG)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"VIDEOHOLE\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(VIDEOHOLE)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"worldmkv\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(worldmkv)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"XLF\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(XLF)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Zero00\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(Zero00)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 3,\n    \"name\": \"LQ (Release Title)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"TeeWee\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(TeeWee)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"BiTOR (2160p)\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"(?=.*?(\\\\b2160p\\\\b))(?=.*?(\\\\bBiTOR\\\\b))\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"BEN THE MEN\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(BEN[ ._-]THE[ ._-]MEN)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 4,\n    \"name\": \"x265 (HD)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"x265/HEVC\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"[xh][ ._-]?265|\\\\bHEVC(\\\\b|\\\\d)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not 2160p\",\n        \"implementation\": \"ResolutionSpecification\",\n        \"implementationName\": \"Resolution\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Resolution\",\n            \"value\": 2160,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 360,\n                \"name\": \"R360P\",\n                \"order\": 360\n              },\n              {\n                \"value\": 480,\n                \"name\": \"R480P\",\n                \"order\": 480\n              },\n              {\n                \"value\": 540,\n                \"name\": \"R540p\",\n                \"order\": 540\n              },\n              {\n                \"value\": 576,\n                \"name\": \"R576p\",\n                \"order\": 576\n              },\n              {\n                \"value\": 720,\n                \"name\": \"R720p\",\n                \"order\": 720\n              },\n              {\n                \"value\": 1080,\n                \"name\": \"R1080p\",\n                \"order\": 1080\n              },\n              {\n                \"value\": 2160,\n                \"name\": \"R2160p\",\n                \"order\": 2160\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 5,\n    \"name\": \"Extras\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Extras\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"(?<=\\\\bS\\\\d+\\\\b).*\\\\b(Extras|Bonus|Extended[ ._-]Clip)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 6,\n    \"name\": \"Repack/Proper\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Repack\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Repack)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Proper\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Proper)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Rerip\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Rerip)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 7,\n    \"name\": \"Repack v2\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Repack v2\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(repack2)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Proper v2\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(proper2)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 8,\n    \"name\": \"Repack v3\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Repack v3\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(repack3)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 9,\n    \"name\": \"4OD\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"4OD\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(4OD)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 10,\n    \"name\": \"ALL4\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"ALL4\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ALL4)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 11,\n    \"name\": \"AMZN\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Amazon\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(amzn|amazon)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 12,\n    \"name\": \"ATVP\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Apple TV+\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(atvp|aptv|Apple TV\\\\+)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 13,\n    \"name\": \"CC\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Comedy Central\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(CC)\\\\b[ ._-]web[ ._-]?(dl|rip)?\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 14,\n    \"name\": \"CRAV\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Crave\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(crav(e)?)\\\\b[ ._-]web[ ._-]?(dl|rip)?\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 15,\n    \"name\": \"DCU\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"DC Universe\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(dcu|DC Universe)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 16,\n    \"name\": \"DSNP\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Disney+\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(dsnp|dsny|disney|Disney\\\\+)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 17,\n    \"name\": \"FOD\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"FOD\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(fod)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 18,\n    \"name\": \"HBO\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"HBO\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(hbo)(?![ ._-]max)\\\\b(?=[ ._-]web[ ._-]?(dl|rip)\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 19,\n    \"name\": \"HMAX\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"HBO Max\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(hmax|hbom|hbo[ ._-]max)\\\\b(?=[ ._-]web[ ._-]?(dl|rip)\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 20,\n    \"name\": \"HULU\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Hulu\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(hulu)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 21,\n    \"name\": \"IP\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"BBC iPlayer\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ip|iplayer)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 22,\n    \"name\": \"iT\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"iTunes\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(it|itunes)\\\\b(?=[ ._-]web[ ._-]?(dl|rip)\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 23,\n    \"name\": \"MAX\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Max\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b((?<!hbo[ ._-])max)\\\\b(?=[ ._-]web[ ._-]?(dl|rip)\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 24,\n    \"name\": \"NF\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Netflix\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(nf|netflix)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 25,\n    \"name\": \"NLZ\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"NLZiet\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(nlz|NLZiet)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 26,\n    \"name\": \"OViD\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"OViD\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ovid)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 27,\n    \"name\": \"PCOK\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Peacock TV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(pcok|Peacock TV)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 28,\n    \"name\": \"PMTP\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Paramount+\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(pmtp|Paramount\\\\+)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 29,\n    \"name\": \"QIBI\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Quibi\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(qibi|quibi)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 30,\n    \"name\": \"RED\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"YouTube Red\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(red|youtube red)\\\\b[ ._-]web[ ._-]?(dl|rip)?\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 31,\n    \"name\": \"SHO\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"SHOWTIME\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(sho|showtime)\\\\b[ ._-]web[ ._-]?(dl|rip)?\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 32,\n    \"name\": \"STAN\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Stan\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(stan)\\\\b[ ._-]web[ ._-]?(dl|rip)?\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 33,\n    \"name\": \"TVer\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"TVer\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(tver)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 34,\n    \"name\": \"U-NEXT\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"U-NEXT\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(u-next)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 35,\n    \"name\": \"VDL\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Videoland\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(vdl|Videoland)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 36,\n    \"name\": \"WEB Tier 01\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"ABBiE\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(ABBiE)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AJP69\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(AJP69)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"APEX\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(APEX|PAXA|PEXA|XEPA)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CasStudio\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(CasStudio)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CRFW\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(CRFW)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CtrlHD\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(CtrlHD)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"FLUX\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(FLUX)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HONE\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(HONE)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KiNGS\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(KiNGS)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"monkee\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(monkee)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NOSiViD\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(NOSiViD)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NTb\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(NTb)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NTG\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(NTG)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"QOQ\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(QOQ)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"RTN\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(RTN)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SiC\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(SiC)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"T6D\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(T6D)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TOMMY\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(TOMMY)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ViSUM\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(ViSUM)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 37,\n    \"name\": \"WEB Tier 02\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"3cTWeB\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(3cTWeB)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"BLUTONiUM\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(BLUTONiUM)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"BTW\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(BTW)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Chotab\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(Chotab)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Cinefeel\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(Cinefeel)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CiT\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(CiT)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CMRG\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(CMRG)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Coo7\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(Coo7)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"dB\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(dB)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DEEP\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(DEEP)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"END\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(END)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ETHiCS\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(ETHiCS)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"FC\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(FC)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Flights\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(Flights)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"GNOME\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(GNOME)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"iJP\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(iJP)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"iKA\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(iKA)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"iT00NZ\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(iT00NZ)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"JETIX\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(JETIX)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KHN\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(KHN)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KiMCHI\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(KiMCHI)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Kitsune\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(Kitsune)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"LAZY\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(LAZY)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MiU\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(MiU)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MZABI\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(MZABI)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NPMS\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(NPMS)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NYH\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(NYH)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"orbitron\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(orbitron)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"PHOENiX\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(PHOENiX)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"playWEB\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(playWEB)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"PSiG\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(PSiG)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ROCCaT\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(ROCCaT)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"RTFM\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(RTFM)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SA89\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(SA89)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SbR\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(SbR)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SDCC\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(SDCC)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SIGMA\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(SIGMA)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SMURF\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(SMURF)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SPiRiT\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(SPiRiT)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TEPES\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(TEPES)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TVSmash\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(TVSmash)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WELP\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(WELP)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"XEBEC\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(XEBEC|4KBEC|CEBEX)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 38,\n    \"name\": \"WEB Tier 03\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"DRACULA\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(DRACULA)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NINJACENTRAL\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(NINJACENTRAL)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SLiGNOME\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(SLiGNOME)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SwAgLaNdEr\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(SwAgLaNdEr)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"T4H\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(T4H)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ViSiON\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(ViSiON)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 39,\n    \"name\": \"WEB Scene\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"DEFLATE\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(DEFLATE)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"INFLATE\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(INFLATE)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 41,\n    \"name\": \"DV HDR10+\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"DV HDR10+\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(?=.*\\\\b(DV|DoVi|Dolby[ .]?V(ision)?)\\\\b)(?=.*\\\\b((HDR10(?=(P(lus)?)\\\\b|\\\\+))))\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV HLG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DV[ .]HLG)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV SDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DV[ .]SDR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 42,\n    \"name\": \"DV HDR10\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"DV HDR10\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(?=.*\\\\b(DV|DoVi|Dolby[ .]?V(ision)?)\\\\b)(?=.*\\\\b((HDR10(?!(P(lus)?)\\\\b|\\\\+))|(HDR))\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV HDR10Plus\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(?=.*\\\\b(DV|DoVi|Dolby[ .]?V(ision)?)\\\\b)(?=.*\\\\b((HDR10(?=(P(lus)?)\\\\b|\\\\+))))\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV HLG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DV[ .]HLG)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV SDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DV[ .]SDR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 43,\n    \"name\": \"DV HLG\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"DV HLG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DV[ .]HLG)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV HDR10\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(?=.*\\\\b(DV|DoVi|Dolby[ .]?V(ision)?)\\\\b)(?=.*\\\\b(HDR(10)?(P(lus)?)?)\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV SDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DV[ .]SDR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 44,\n    \"name\": \"DV SDR\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"DV SDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DV[ .]SDR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV HDR10\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(?=.*\\\\b(DV|DoVi|Dolby[ .]?V(ision)?)\\\\b)(?=.*\\\\b(HDR(10)?(P(lus)?)?)\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV HLG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DV[ .]HLG)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 45,\n    \"name\": \"DV\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"DV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(dv|dovi|dolby[ .]?v(ision)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV HDR10\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(?=.*\\\\b(DV|DoVi|Dolby[ .]?V(ision)?)\\\\b)(?=.*\\\\b(HDR(10)?(P(lus)?)?)\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV HLG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DV[ .]HLG)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV SDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DV[ .]SDR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 46,\n    \"name\": \"HDR (undefined)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"RlsGrp (Missing HDR)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(FraMeSToR|HQMUX|SiCFoI)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"2160p\",\n        \"implementation\": \"ResolutionSpecification\",\n        \"implementationName\": \"Resolution\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Resolution\",\n            \"value\": 2160,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 360,\n                \"name\": \"R360P\",\n                \"order\": 360\n              },\n              {\n                \"value\": 480,\n                \"name\": \"R480P\",\n                \"order\": 480\n              },\n              {\n                \"value\": 540,\n                \"name\": \"R540p\",\n                \"order\": 540\n              },\n              {\n                \"value\": 576,\n                \"name\": \"R576p\",\n                \"order\": 576\n              },\n              {\n                \"value\": 720,\n                \"name\": \"R720p\",\n                \"order\": 720\n              },\n              {\n                \"value\": 1080,\n                \"name\": \"R1080p\",\n                \"order\": 1080\n              },\n              {\n                \"value\": 2160,\n                \"name\": \"R2160p\",\n                \"order\": 2160\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(dv|dovi|dolby[ .]?v(ision)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HDR10\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHDR10(\\\\b[^+|Plus])\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HDR10+\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHDR10(\\\\+|P(lus)?\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HLG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HLG)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not PQ\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(PQ)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not SDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bSDR(\\\\b|\\\\d)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 47,\n    \"name\": \"HDR\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"HDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HDR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(dv|dovi|dolby[ .]?v(ision)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HDR10\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHDR10(\\\\b[^+|Plus])\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HDR10+\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHDR10(\\\\+|P(lus)?\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HLG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHLG(\\\\b|\\\\d)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not PQ\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(PQ)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not RlsGrp (Missing HDR)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(FraMeSToR|HQMUX|SiCFoI)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not SDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bSDR(\\\\b|\\\\d)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 48,\n    \"name\": \"HDR10\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"HDR10\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHDR10(\\\\b[^+|Plus])\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV HDR10\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(?=.*\\\\b(DV|DoVi|Dolby[ .]?V(ision)?)\\\\b)(?=.*\\\\b(HDR(10)?(P(lus)?)?)\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not PQ\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(PQ)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HLG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HLG)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not SDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bSDR(\\\\b|\\\\d)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(dv|dovi|dolby[ .]?v(ision)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 49,\n    \"name\": \"HDR10+\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"HDR10+\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHDR10(\\\\+|P(lus)?\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV HDR10\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(?=.*\\\\b(DV|DoVi|Dolby[ .]?V(ision)?)\\\\b)(?=.*\\\\b(HDR(10)?(P(lus)?)?)\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not PQ\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(PQ)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HLG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HLG)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not SDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bSDR(\\\\b|\\\\d)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(dv|dovi|dolby[ .]?v(ision)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 50,\n    \"name\": \"HLG\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"HLG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HLG)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(dv|dovi|dolby[ .]?v(ision)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HDR10+\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHDR10(\\\\+|P(lus)?\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HDR10\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHDR10(\\\\b[^+|Plus])\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not PQ\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(PQ)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 51,\n    \"name\": \"PQ\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"PQ\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(PQ)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not DV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(dv|dovi|dolby[ .]?v(ision)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HDR10\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHDR10(\\\\b[^+|Plus])\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HDR10+\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHDR10(\\\\+|P(lus)?\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HLG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HLG)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not SDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bSDR(\\\\b|\\\\d)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 52,\n    \"name\": \"UHD Streaming Boost\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Peacock TV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(pcok|Peacock TV)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Paramount+\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(pmtp|Paramount\\\\+)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Hulu\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(hulu)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"2160p\",\n        \"implementation\": \"ResolutionSpecification\",\n        \"implementationName\": \"Resolution\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Resolution\",\n            \"value\": 2160,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 360,\n                \"name\": \"R360P\",\n                \"order\": 360\n              },\n              {\n                \"value\": 480,\n                \"name\": \"R480P\",\n                \"order\": 480\n              },\n              {\n                \"value\": 540,\n                \"name\": \"R540p\",\n                \"order\": 540\n              },\n              {\n                \"value\": 576,\n                \"name\": \"R576p\",\n                \"order\": 576\n              },\n              {\n                \"value\": 720,\n                \"name\": \"R720p\",\n                \"order\": 720\n              },\n              {\n                \"value\": 1080,\n                \"name\": \"R1080p\",\n                \"order\": 1080\n              },\n              {\n                \"value\": 2160,\n                \"name\": \"R2160p\",\n                \"order\": 2160\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 53,\n    \"name\": \"UHD Streaming Cut\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Amazon\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(amzn|amazon)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HBO Max\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(hmax|hbom|hbo[ ._-]max)\\\\b(?=[ ._-]web[ ._-]?(dl|rip)\\\\b)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Stan\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(stan)\\\\b[ ._-]web[ ._-]?(dl|rip)?\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"2160p\",\n        \"implementation\": \"ResolutionSpecification\",\n        \"implementationName\": \"Resolution\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Resolution\",\n            \"value\": 2160,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 360,\n                \"name\": \"R360P\",\n                \"order\": 360\n              },\n              {\n                \"value\": 480,\n                \"name\": \"R480P\",\n                \"order\": 480\n              },\n              {\n                \"value\": 540,\n                \"name\": \"R540p\",\n                \"order\": 540\n              },\n              {\n                \"value\": 576,\n                \"name\": \"R576p\",\n                \"order\": 576\n              },\n              {\n                \"value\": 720,\n                \"name\": \"R720p\",\n                \"order\": 720\n              },\n              {\n                \"value\": 1080,\n                \"name\": \"R1080p\",\n                \"order\": 1080\n              },\n              {\n                \"value\": 2160,\n                \"name\": \"R2160p\",\n                \"order\": 2160\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 54,\n    \"name\": \"Anime BD Tier 01 (Top SeaDex Muxers)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Bluray\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 6,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Bluray Remux\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 7,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DVD\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 5,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Aergia\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Aergia\\\\]|-Aergia(?!-raws)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Legion\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Legion\\\\]|-Legion\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"LYS1TH3A\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(LYS1TH3A)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"OZR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(OZR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"sam\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[sam\\\\]|-sam\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SCY\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SCY)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"smol\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[smol\\\\]|-smol\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Vanilla\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Vanilla\\\\]|-Vanilla\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Vodes\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Vodes)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 55,\n    \"name\": \"Anime BD Tier 02 (SeaDex Muxers)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Bluray\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 6,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Bluray Remux\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 7,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DVD\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 5,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"0x539\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(0x539)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Alt\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Alt\\\\]|-Alt\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ARC\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[ARC\\\\]|-ARC\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Arid\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Arid\\\\]|-Arid\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"aro\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(aro)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Baws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Baws)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"BKC\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(BKC)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Brrrrrrr\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Brrrrrrr)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Chotab\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Chotab)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Crow\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Crow\\\\]|-Crow\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CsS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(CsS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CUNNY\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(CUNNY)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"D-Z0N3\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(D-Z0N3)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Dae\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Dae)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Datte13\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Datte13)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Drag\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Drag\\\\]|-Drag\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"FLFL\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(FLFL)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"hydes\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(hydes)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"iKaos\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(iKaos)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"JySzE\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(JySzE)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"LostYears\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(LostYears)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Lulu\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Lulu\\\\]|-Lulu\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Matsya\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Matsya)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MC\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(MC)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Metal\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Metal\\\\]|-Metal\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MTBB\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(MTBB)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Noyr\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Noyr)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NSDAB\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(NSDAB)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"pog42\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(pog42)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"pyroneko\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(pyroneko)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"RAI\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(RAI)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Reza\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Reza)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Shimatta\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Shimatta)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Smoke\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Smoke\\\\]|-Smoke\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Spirale\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Spirale)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Thighs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Thighs\\\\]|-Thighs\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"UDF\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(UDF)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Yuki\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Yuki\\\\]|-Yuki\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 56,\n    \"name\": \"Anime BD Tier 03 (SeaDex Muxers)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Bluray\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 6,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Bluray Remux\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 7,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DVD\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 5,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AC\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[AC\\\\]|-AC\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ASC\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ASC)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AssMix\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AssMix)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Ayashii\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Ayashii)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CBT\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(CBT)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CTR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(CTR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CyC\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(CyC)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Dekinai\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Dekinai\\\\]|-Dekinai\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"EXP\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[EXP\\\\]|-EXP\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Galator\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Galator)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"GSK_kun\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(GSK[._-]kun)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Holomux\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Holomux)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"IK\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(IK)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Kaizoku\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AnimeKaizoku)\\\\b|\\\\[Kaizoku\\\\]|-Kaizoku\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Kametsu\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Kametsu)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KH\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(KH)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"kuchikirukia\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(kuchikirukia)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"LazyRemux\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(LazyRemux)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MK\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(MK)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Mysteria\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Mysteria\\\\]|-Mysteria\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Netaro\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Netaro)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Pn8\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Pn8)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Pookie\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Pookie)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Quetzal\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Quetzal)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Rasetsu\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Rasetsu)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Senjou\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Senjou\\\\]|-Senjou\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ShowY\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ShowY)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WBDP\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(WBDP)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WSE\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(WSE)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Yoghurt\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Yoghurt)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"YURI\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[YURI\\\\]|-YURI\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ZOIO\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ZOIO)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 57,\n    \"name\": \"Anime BD Tier 04 (SeaDex Muxers)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Bluray\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 6,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Bluray Remux\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 7,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DVD\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 5,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"deanzel\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(deanzel)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ShadyCrab\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ShadyCrab)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"hchcsen\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(hchcsen)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NH\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(NH)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Chimera\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Chimera\\\\]|-Chimera\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Bulldog\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Bulldog\\\\]|-Bulldog\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Foxtrot\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Foxtrot\\\\]|-Foxtrot\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Koten_Gars\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Koten[ ._-]Gars)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Kulot\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Kulot)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Asakura\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Asakura\\\\]|-Asakura\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HaiveMind\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HaiveMind)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"mottoj\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(mottoj)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Bolshevik\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Bolshevik\\\\]|-Bolshevik\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Scriptum\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Scriptum)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SOLA\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[SOLA\\\\]|-SOLA\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NTRM\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(NTRM)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ASO\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ASO)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MCLR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(MCLR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"D3\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(D3)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AOmundson\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AOmundson)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"RMX\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(RMX)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"karios\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(karios)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"xPearse\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(xPearse)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"kBaraka\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(kBaraka)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SNSbu\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SNSbu)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Orphan\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Orphan\\\\]|-Orphan\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Cait-Sidhe\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Cait-Sidhe)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"THORA\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(THORA)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Davinci\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Davinci\\\\]|-Davinci\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"GHS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(GHS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Iznjie Biznjie\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Iznjie[ .-]Biznjie)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"9volt\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(9volt)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Lia\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Lia\\\\]|-Lia\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"kmplx\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(kmplx)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"UWU\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(UWU)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Koitern\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Koitern)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Commie\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Commie)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Kaleido\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Kaleido)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Doki\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Doki\\\\]|-Doki\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Tsundere\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Tsundere\\\\]|-Tsundere(?!-)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Chihiro\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Chihiro\\\\]|-Chihiro\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SallySubs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SallySubs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CoalGirls\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(CoalGirls)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 58,\n    \"name\": \"Anime BD Tier 05 (Remuxes)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Bluray\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 6,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Bluray Remux\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 7,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DVD\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 5,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ANThELIa\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ANThELIa)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AP\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AP)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"BluDragon\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(BluDragon)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"D4C\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(D4C)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Dragon-Releases\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Dragon-Releases)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"E.N.D\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(E[.-]N[.-]D)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KAWAiREMUX\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(KAWAiREMUX)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MKVULTRA\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(MKVULTRA)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Raizel\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Raizel)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"REVO\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(REVO)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Spark\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Spark\\\\]|-Spark\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SRLS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SRLS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TTGA\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(TTGA)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ZR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ZR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 59,\n    \"name\": \"Anime BD Tier 06 (FanSubs)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Bluray\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 6,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Bluray Remux\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 7,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DVD\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 5,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Afro\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Afro\\\\]|-Afro\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Akai\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Akai\\\\]|-Akai\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Almighty\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Almighty\\\\]|-Almighty\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ANE\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ANE)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Asenshi\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Asenshi)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"BlurayDesuYo\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(BlurayDesuYo)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Bunny-Apocalypse\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Bunny-Apocalypse)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CH\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[CH\\\\]|-CH\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"EJF\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(EJF)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Exiled-Destiny\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Exiled-Destiny|E-D)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"FFF\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(FFF)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Final8\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Final8)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"GS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(GS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Harunatsu\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Harunatsu\\\\]|-Harunatsu\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Impatience\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Impatience\\\\]|-Impatience\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Inka-Subs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Inka-Subs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Judgement\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Judgment\\\\]|-Judgment\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Kantai\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Kantai\\\\]|-Kantai\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"LCE\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(LCE)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Licca\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Licca)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Nii-sama\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Nii-sama\\\\]|-Nii-sama\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"niizk\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(niizk)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Nishi-Taku\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Nishi-Taku)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"OnDeed\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(OnDeed)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"orz\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(orz)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"PAS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(PAS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"peachflavored\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(peachflavored)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Saizen\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Saizen)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SCP-2223\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SCP-2223)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SHiN-gx\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SHiN-gx)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SmugCat\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SmugCat)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Soldado\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Soldado\\\\]|-Soldado\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Sushi\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Sushi\\\\]|-Sushi\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Vivid\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Vivid\\\\]|-Vivid\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Watashi\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Watashi\\\\]|-Watashi\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Yabai\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Yabai\\\\]|-Yabai\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Zurako\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Zurako)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 60,\n    \"name\": \"Anime BD Tier 07 (P2P/Scene)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Bluray\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 6,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Bluray Remux\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 7,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DVD\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 5,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"A-L\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(A-L)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ANiHLS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ANiHLS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CBM\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(CBM)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DHD\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DHD)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DragsterPS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DragsterPS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HAiKU\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HAiKU)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Hark0N\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Hark0N)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"iAHD\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(iAHD)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"inid4c\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(inid4c)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KiyoshiStar\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(KS|KiyoshiStar)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MCR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(MCR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NPC\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[NPC\\\\]|-NPC\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"RedBlade\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(RedBlade)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"RH\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(RH)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SEV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SEV)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"STRiFE\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[STRiFE\\\\]|-STRiFE\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TENEIGHTY\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(TENEIGHTY)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WaLMaRT\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(WaLMaRT)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 61,\n    \"name\": \"Anime BD Tier 08 (Mini Encodes)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Bluray\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 6,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Bluray Remux\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 7,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DVD\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 5,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AkihitoSubs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AkihitoSubs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Arukoru\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Arukoru)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"EDGE\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[EDGE\\\\]|-EDGE\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"EMBER\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[EMBER\\\\]|-EMBER\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"GHOST\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[GHOST\\\\]|-GHOST\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Judas\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Judas\\\\]|-Judas\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"naiyas\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[naiyas\\\\]|-naiyas\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Nep_Blanc\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Nep[ ._-]Blanc)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Prof\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Prof\\\\]|-Prof\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Shirσ\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Shirσ)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"YURASAKA\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[YURASUKA\\\\]|-YURASUKA\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 62,\n    \"name\": \"Anime Web Tier 01 (Muxers)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Arg0\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Arg0)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Arid\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Arid\\\\]|-Arid\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Baws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Baws)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"LostYears\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(LostYears)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"LYS1TH3A\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(LYS1TH3A)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"sam\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[sam\\\\]|-sam\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SCY\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SCY)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Setsugen\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Setsugen)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"smol\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[smol\\\\]|-smol\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Vodes\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Vodes)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Z4ST1N\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Z4ST1N)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 63,\n    \"name\": \"Anime Web Tier 02 (Top FanSubs)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"0x539\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(0x539)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Asakura\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Asakura\\\\]|-Asakura\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Cyan\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Cyan\\\\]|-Cyan\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Dae\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Dae\\\\]|-Dae\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Foxtrot\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Foxtrot\\\\]|-Foxtrot\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Gao\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Gao\\\\]|-Gao\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"GSK_kun\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(GSK[._-]kun)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HatSubs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HatSubs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MTBB\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(MTBB)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Okay-Subs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Okay-Subs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Pizza\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Pizza\\\\]|-Pizza\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Reza\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Reza)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Slyfox\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Slyfox)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SoLCE\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SoLCE)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Tenshi\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[tenshi\\\\]|-tenshi\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 64,\n    \"name\": \"Anime Web Tier 03 (Official Subs)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SubsPlease\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SubsPlease)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SubsPlus+\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SubsPlus\\\\+?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ZR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ZR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 65,\n    \"name\": \"Anime Web Tier 04 (Official Subs)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"BlueLobster\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(BlueLobster)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Erai-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Erai-raws)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"GST\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(GST)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HorribleRips\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HorribleRips)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HorribleSubs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HorribleSubs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KAN3D2M\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(KAN3D2M)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KiyoshiStar\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(KS|KiyoshiStar)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Lia\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Lia\\\\]|-Lia\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NanDesuKa\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(NanDesuKa)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"URANIME\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(URANIME)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"VARYG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(VARYG)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ZigZag\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[ZigZag\\\\]|-ZigZab\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 66,\n    \"name\": \"Anime Web Tier 05 (FanSubs)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"9volt\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(9volt)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"GJM\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(GJM)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Kaleido\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Kaleido)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Kantai\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Kantai\\\\]|-Kantai\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 67,\n    \"name\": \"Anime Web Tier 06 (FanSubs)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Asenshi\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Asenshi)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Chihiro\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Chihiro\\\\]|-Chihiro\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Commie\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Commie)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DameDesuYo\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DameDesuYo)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Doki\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Doki\\\\]|-Doki\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Tsundere\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Tsundere\\\\]|-Tsundere(?!-)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 68,\n    \"name\": \"Anime LQ Groups\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"$tore-Chill\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(\\\\$tore-Chill)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"0neshot\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(0neshot)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"224\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[224\\\\]|-224\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"A-Destiny\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(A-Destiny)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AceAres\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AceAres)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AhmadDev\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AhmadDev)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Anime Chap\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Anime[ .-]?Chap)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Anime Land\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Anime[ .-]?Land)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Anime Time\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Anime[ .-]?Time)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AnimeDynastyEN\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AnimeDynastyEN)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AnimeKuro\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AnimeKuro)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AnimeRG\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AnimeRG)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Animesubs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Animesubs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AnimeTR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AnimeTR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AniVoid\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AniVoid)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ArataEnc\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ArataEnc)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AREY\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(AREY)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Ari\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Ari\\\\]|-Ari\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ASW\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ASW)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"BJX\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(BJX)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"BlackLuster\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(BlackLuster)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"bonkai77\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(bonkai77)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CameEsp\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(CameEsp)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Cat66\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Cat66)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CBB\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(CBB)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Cerberus\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Cerberus\\\\]|-Cerberus\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Cleo\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Cleo\\\\]|-Cleo\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"CuaP\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(CuaP)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DaddySubs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Daddy(Subs)?\\\\]|-Daddy(Subs)?\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DARKFLiX\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DARKFLiX)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DB\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[DB\\\\]\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DBArabic\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DBArabic)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Deadmau- RAWS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Deadmau[ .-]?[ .-]?RAWS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DKB\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DKB)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DP\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DP)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DsunS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(DsunS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ExREN\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ExREN)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"FAV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[FAV\\\\]|-FAV\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Fish\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b((Baked|Dead|Space)Fish)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"FunArts\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(FunArts)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"GERMini\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(GERMini)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Hakata Ramen\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Hakata[ .-]?Ramen)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Hall_of_C\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Hall_of_C)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Hatsuyuki\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Hatsuyuki\\\\]|-Hatsuyuki\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HAV1T\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HAV1T)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HENiL\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HENiL)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Hitoku\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Hitoku\\\\]|-Hitoki\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HollowRoxas\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HollowRoxas)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HR)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ICEBLUE\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ICEBLUE)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"iPUNISHER\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(iPUNISHER)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"JacobSwaggedUp\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(JacobSwaggedUp)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Johnny-englishsubs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Johnny-englishsubs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Kaerizaki-Fansub\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Kaerizaki-Fansub)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Kanjouteki\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Kanjouteki)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KEKMASTERS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(KEKMASTERS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Kirion\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Kirion)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KQRM\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(KQRM)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KRP\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(KRP)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"LoliHouse\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(LoliHouse)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"M@nI\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(M@nI)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"mal lu zen\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(mal[ .-]lu[ .-]zen)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Man.K\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Man\\\\.K)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Maximus\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Maximus\\\\]|-Maximus\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MD\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[MD\\\\]|-MD\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"mdcx\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(mdcx)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Metaljerk\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Metaljerk)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MGD\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(MGD)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MiniFreeza\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(MiniFreeza)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MinisCuba\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(MinisCuba)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MiniTheatre\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(MiniTheatre)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Mites\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Mites)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Modders Bay\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Modders[ .-]?Bay)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Mr. Deadpool\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Mr\\\\.Deadpool)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NemDiggers\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(NemDiggers)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"neoHEVC\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(neoHEVC)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Nokou\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Nokou)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NoobSubs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(N[eo][wo]b[ ._-]?Subs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(NS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Nyanpasu\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Nyanpasu)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"OldCastle\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(OldCastle)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Pantsu\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Pantsu\\\\]|-Pantsu\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Pao\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Pao\\\\]|-Pao\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"phazer11\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(phazer11)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Pixel\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Pixel\\\\]|-Pixel\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Plex Friendly\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Plex[ .-]?Friendly)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"PnPSubs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(PnPSubs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Polarwindz\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Polarwindz)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Project-gxs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Project-gxs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"PuyaSubs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(PuyaSubs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"QaS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(QAS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"QCE\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(QCE)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Rando235\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Rando235)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Ranger\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Ranger\\\\]|-Ranger\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Rapta\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Rapta\\\\]|-Rapta\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Raw Files\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(M2TS|BDMV|BDVD)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Raze\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Raze\\\\]|-Raze\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Reaktor\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Reaktor)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"RightShiftBy2\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(RightShiftBy2)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Rip Time\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Rip[ .-]?Time)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SAD\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[SAD\\\\]|-SAD\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Salieri\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Salieri)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Samir755\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Samir755)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SanKyuu\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SanKyuu)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SEiN\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[SEiN\\\\]|-SEiN\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"sekkusu&ok\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(sekkusu&ok)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SHFS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SHFS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SLAX\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SLAX)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SRW\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SRW)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SSA\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SSA)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"StrayGods\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(StrayGods)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Suki Desu\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Suki[ .-]?Desu\\\\]|-Suki[ .-]?Desu\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TeamTurquoize\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(TeamTurquoize)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Tenrai Sensei\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Tenrai[ .-]?Sensei)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TnF\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(TnF)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TOPKEK\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(TOPKEK)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Trix\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Trix\\\\]|-Trix\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"U3-Web\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(U3-Web)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"UNBIASED\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[UNBIASED\\\\]|-UNBIASED\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"USD\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[USD\\\\]|-USD\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Valenciano\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Valenciano)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"VipapkStudios\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(VipapkStudios)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Wardevil\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Wardevil\\\\]|-Wardevil\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WtF Anime\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(WtF[ ._-]?Anime)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"xiao-av1\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(xiao-av1)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Yabai_Desu_NeRandomRemux\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Yabai_Desu_NeRandomRemux)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"YakuboEncodes\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(YakuboEncodes)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"youshikibi\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(youshikibi)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"YuiSubs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(YuiSubs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Yun\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Yun\\\\]|-Yun\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"zza\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[zza\\\\]|-zza\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 69,\n    \"name\": \"Anime Raws\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Beatrice-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Beatrice[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Daddy-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Daddy[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Fumi-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Fumi[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"IrizaRaws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Iriza[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Kawaiika-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Kawaiika[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"km\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[km\\\\]|-km\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Koi-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Koi[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Lilith-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Lilith[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"LowPower-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"LowPower[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NanakoRaws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Nanako[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"NC-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"NC[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"neko-raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"neko[ ._-]?(raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"New-raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"New[ ._-]?(raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Ohys-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Ohys[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Pandoratv-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Pandoratv[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Raws-Maji\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Raws-Maji)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Scryous-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Scryous[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Seicher-Raws\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Seicher[ ._-]?(Raws)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 70,\n    \"name\": \"Dubs Only\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Dubbed\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"(?<!multi-)\\\\b(dub(bed)?)\\\\b|(funi|eng(lish)?)_?dub\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Golumpa\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Golumpa)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KaiDubs\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(KaiDubs)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KamiFS\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(KamiFS)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KS (Not Dual Audio)\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(?!.*Dual[-_. ]?Audio).*\\\\bKS\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"torenter69\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(torenter69)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Yameii\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[Yameii\\\\]|-Yameii\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 71,\n    \"name\": \"v0\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"v0\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"(\\\\b|\\\\d)(v0)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 72,\n    \"name\": \"v1\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"v1\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"(\\\\b|\\\\d)(v1)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 73,\n    \"name\": \"v2\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"v2\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"(\\\\b|\\\\d)(v2)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 74,\n    \"name\": \"v3\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"v3\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"(\\\\b|\\\\d)(v3)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 75,\n    \"name\": \"v4\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"v4\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"(\\\\b|\\\\d)(v4)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 76,\n    \"name\": \"AV1\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"AV1\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bAV1\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 77,\n    \"name\": \"VOSTFR\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"VOSTFR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(VOST.*?FR(E|A)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SUBFRENCH\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(SUBFR(A|ENCH)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 78,\n    \"name\": \"CR\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Crunchyroll\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(C(runchy)?[ .-]?R(oll)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 79,\n    \"name\": \"FUNi\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Funimation\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(FUNi(mation)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 80,\n    \"name\": \"VRV\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"VRV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(VRV)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 81,\n    \"name\": \"ADN\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"ADN\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ADN)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 82,\n    \"name\": \"B-Global\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"B-Global\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(B[ .-]?Global)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 83,\n    \"name\": \"Bilibili\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Bilibili\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Bilibili)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 84,\n    \"name\": \"HIDIVE\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HIDIVE\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(HIDI(VE)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 85,\n    \"name\": \"ABEMA\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"ABEMA\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(ABEMA[ ._-]?(TV)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEB\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 86,\n    \"name\": \"Remux Tier 01\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Remux\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 7,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"BLURANiUM\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(BLURANiUM)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"BMF\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(BMF)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"FraMeSToR\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(FraMeSToR)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"PmP\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(PmP)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SiCFoI\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(SiCFoI)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 87,\n    \"name\": \"Remux Tier 02\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Remux\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 7,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"decibeL\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(decibeL)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"EPSiLON\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(EPSiLON)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HiFi\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(HiFi)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"KRaLiMaRKo\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(KRaLiMaRKo)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"playBD\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(playBD)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"PTer\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(PTer)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TRiToN\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"^(TRiToN)$\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 88,\n    \"name\": \"DV (WEBDL)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Dolby Vision\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(dv|dovi|dolby[ .]?v(ision)?)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBDL\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 3,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WEBRIP\",\n        \"implementation\": \"SourceSpecification\",\n        \"implementationName\": \"Source\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Source\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"Television\",\n                \"order\": 1\n              },\n              {\n                \"value\": 2,\n                \"name\": \"TelevisionRaw\",\n                \"order\": 2\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Web\",\n                \"order\": 3\n              },\n              {\n                \"value\": 4,\n                \"name\": \"WebRip\",\n                \"order\": 4\n              },\n              {\n                \"value\": 5,\n                \"name\": \"DVD\",\n                \"order\": 5\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Bluray\",\n                \"order\": 6\n              },\n              {\n                \"value\": 7,\n                \"name\": \"BlurayRaw\",\n                \"order\": 7\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not RlsGrp\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Flights)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHDR(\\\\b|\\\\d)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not Hulu\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(hulu)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 89,\n    \"name\": \"SDR\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"2160p\",\n        \"implementation\": \"ResolutionSpecification\",\n        \"implementationName\": \"Resolution\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Resolution\",\n            \"value\": 2160,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 360,\n                \"name\": \"R360P\",\n                \"order\": 360\n              },\n              {\n                \"value\": 480,\n                \"name\": \"R480P\",\n                \"order\": 480\n              },\n              {\n                \"value\": 540,\n                \"name\": \"R540p\",\n                \"order\": 540\n              },\n              {\n                \"value\": 576,\n                \"name\": \"R576p\",\n                \"order\": 576\n              },\n              {\n                \"value\": 720,\n                \"name\": \"R720p\",\n                \"order\": 720\n              },\n              {\n                \"value\": 1080,\n                \"name\": \"R1080p\",\n                \"order\": 1080\n              },\n              {\n                \"value\": 2160,\n                \"name\": \"R2160p\",\n                \"order\": 2160\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HDR Formats\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bHDR(\\\\b|\\\\d)|\\\\b(dv|dovi|dolby[ .]?v(ision)?)\\\\b|\\\\b(FraMeSToR|HQMUX|SICFoI)\\\\b|\\\\b(PQ)\\\\b|\\\\bHLG(\\\\b|\\\\d)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SDR\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\bSDR\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 90,\n    \"name\": \"Uncensored\",\n    \"includeCustomFormatWhenRenaming\": true,\n    \"specifications\": [\n      {\n        \"name\": \"Uncensored\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(Uncut|Unrated|Uncensored|AT[-_. ]?X)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 91,\n    \"name\": \"10bit\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"10bit\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"10[.-]?bit\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"hi10p\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"hi10p\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 92,\n    \"name\": \"Anime Dual Audio\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Dual Audio\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"dual[ ._-]?audio|[\\\\[(]dual[\\\\])]|(JA|ZH)\\\\+EN|EN\\\\+(JA|ZH)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not Single Language Only\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\[(JA|ZH)\\\\]\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Japanese Language\",\n        \"implementation\": \"LanguageSpecification\",\n        \"implementationName\": \"Language\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Language\",\n            \"value\": 8,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": -2,\n                \"name\": \"Original\",\n                \"order\": 0\n              },\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 26,\n                \"name\": \"Arabic\",\n                \"order\": 0\n              },\n              {\n                \"value\": 41,\n                \"name\": \"Bosnian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 28,\n                \"name\": \"Bulgarian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 38,\n                \"name\": \"Catalan\",\n                \"order\": 0\n              },\n              {\n                \"value\": 10,\n                \"name\": \"Chinese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 39,\n                \"name\": \"Croatian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 25,\n                \"name\": \"Czech\",\n                \"order\": 0\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Danish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 7,\n                \"name\": \"Dutch\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"English\",\n                \"order\": 0\n              },\n              {\n                \"value\": 42,\n                \"name\": \"Estonian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 16,\n                \"name\": \"Finnish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 19,\n                \"name\": \"Flemish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 2,\n                \"name\": \"French\",\n                \"order\": 0\n              },\n              {\n                \"value\": 4,\n                \"name\": \"German\",\n                \"order\": 0\n              },\n              {\n                \"value\": 20,\n                \"name\": \"Greek\",\n                \"order\": 0\n              },\n              {\n                \"value\": 23,\n                \"name\": \"Hebrew\",\n                \"order\": 0\n              },\n              {\n                \"value\": 27,\n                \"name\": \"Hindi\",\n                \"order\": 0\n              },\n              {\n                \"value\": 22,\n                \"name\": \"Hungarian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 9,\n                \"name\": \"Icelandic\",\n                \"order\": 0\n              },\n              {\n                \"value\": 44,\n                \"name\": \"Indonesian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 5,\n                \"name\": \"Italian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 8,\n                \"name\": \"Japanese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 21,\n                \"name\": \"Korean\",\n                \"order\": 0\n              },\n              {\n                \"value\": 36,\n                \"name\": \"Latvian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 24,\n                \"name\": \"Lithuanian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 45,\n                \"name\": \"Macedonian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 29,\n                \"name\": \"Malayalam\",\n                \"order\": 0\n              },\n              {\n                \"value\": 15,\n                \"name\": \"Norwegian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 37,\n                \"name\": \"Persian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 12,\n                \"name\": \"Polish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 18,\n                \"name\": \"Portuguese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 33,\n                \"name\": \"Portuguese (Brazil)\",\n                \"order\": 0\n              },\n              {\n                \"value\": 35,\n                \"name\": \"Romanian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 11,\n                \"name\": \"Russian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 40,\n                \"name\": \"Serbian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 31,\n                \"name\": \"Slovak\",\n                \"order\": 0\n              },\n              {\n                \"value\": 46,\n                \"name\": \"Slovenian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Spanish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 34,\n                \"name\": \"Spanish (Latino)\",\n                \"order\": 0\n              },\n              {\n                \"value\": 14,\n                \"name\": \"Swedish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 43,\n                \"name\": \"Tamil\",\n                \"order\": 0\n              },\n              {\n                \"value\": 32,\n                \"name\": \"Thai\",\n                \"order\": 0\n              },\n              {\n                \"value\": 17,\n                \"name\": \"Turkish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 30,\n                \"name\": \"Ukrainian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 13,\n                \"name\": \"Vietnamese\",\n                \"order\": 0\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Chinese Language\",\n        \"implementation\": \"LanguageSpecification\",\n        \"implementationName\": \"Language\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Language\",\n            \"value\": 10,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": -2,\n                \"name\": \"Original\",\n                \"order\": 0\n              },\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 26,\n                \"name\": \"Arabic\",\n                \"order\": 0\n              },\n              {\n                \"value\": 41,\n                \"name\": \"Bosnian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 28,\n                \"name\": \"Bulgarian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 38,\n                \"name\": \"Catalan\",\n                \"order\": 0\n              },\n              {\n                \"value\": 10,\n                \"name\": \"Chinese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 39,\n                \"name\": \"Croatian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 25,\n                \"name\": \"Czech\",\n                \"order\": 0\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Danish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 7,\n                \"name\": \"Dutch\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"English\",\n                \"order\": 0\n              },\n              {\n                \"value\": 42,\n                \"name\": \"Estonian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 16,\n                \"name\": \"Finnish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 19,\n                \"name\": \"Flemish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 2,\n                \"name\": \"French\",\n                \"order\": 0\n              },\n              {\n                \"value\": 4,\n                \"name\": \"German\",\n                \"order\": 0\n              },\n              {\n                \"value\": 20,\n                \"name\": \"Greek\",\n                \"order\": 0\n              },\n              {\n                \"value\": 23,\n                \"name\": \"Hebrew\",\n                \"order\": 0\n              },\n              {\n                \"value\": 27,\n                \"name\": \"Hindi\",\n                \"order\": 0\n              },\n              {\n                \"value\": 22,\n                \"name\": \"Hungarian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 9,\n                \"name\": \"Icelandic\",\n                \"order\": 0\n              },\n              {\n                \"value\": 44,\n                \"name\": \"Indonesian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 5,\n                \"name\": \"Italian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 8,\n                \"name\": \"Japanese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 21,\n                \"name\": \"Korean\",\n                \"order\": 0\n              },\n              {\n                \"value\": 36,\n                \"name\": \"Latvian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 24,\n                \"name\": \"Lithuanian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 45,\n                \"name\": \"Macedonian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 29,\n                \"name\": \"Malayalam\",\n                \"order\": 0\n              },\n              {\n                \"value\": 15,\n                \"name\": \"Norwegian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 37,\n                \"name\": \"Persian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 12,\n                \"name\": \"Polish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 18,\n                \"name\": \"Portuguese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 33,\n                \"name\": \"Portuguese (Brazil)\",\n                \"order\": 0\n              },\n              {\n                \"value\": 35,\n                \"name\": \"Romanian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 11,\n                \"name\": \"Russian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 40,\n                \"name\": \"Serbian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 31,\n                \"name\": \"Slovak\",\n                \"order\": 0\n              },\n              {\n                \"value\": 46,\n                \"name\": \"Slovenian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Spanish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 34,\n                \"name\": \"Spanish (Latino)\",\n                \"order\": 0\n              },\n              {\n                \"value\": 14,\n                \"name\": \"Swedish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 43,\n                \"name\": \"Tamil\",\n                \"order\": 0\n              },\n              {\n                \"value\": 32,\n                \"name\": \"Thai\",\n                \"order\": 0\n              },\n              {\n                \"value\": 17,\n                \"name\": \"Turkish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 30,\n                \"name\": \"Ukrainian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 13,\n                \"name\": \"Vietnamese\",\n                \"order\": 0\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 94,\n    \"name\": \"Language: Not German or English\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Not German\",\n        \"implementation\": \"LanguageSpecification\",\n        \"implementationName\": \"Language\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Language\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": -2,\n                \"name\": \"Original\",\n                \"order\": 0\n              },\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 26,\n                \"name\": \"Arabic\",\n                \"order\": 0\n              },\n              {\n                \"value\": 41,\n                \"name\": \"Bosnian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 28,\n                \"name\": \"Bulgarian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 38,\n                \"name\": \"Catalan\",\n                \"order\": 0\n              },\n              {\n                \"value\": 10,\n                \"name\": \"Chinese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 39,\n                \"name\": \"Croatian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 25,\n                \"name\": \"Czech\",\n                \"order\": 0\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Danish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 7,\n                \"name\": \"Dutch\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"English\",\n                \"order\": 0\n              },\n              {\n                \"value\": 42,\n                \"name\": \"Estonian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 16,\n                \"name\": \"Finnish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 19,\n                \"name\": \"Flemish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 2,\n                \"name\": \"French\",\n                \"order\": 0\n              },\n              {\n                \"value\": 4,\n                \"name\": \"German\",\n                \"order\": 0\n              },\n              {\n                \"value\": 20,\n                \"name\": \"Greek\",\n                \"order\": 0\n              },\n              {\n                \"value\": 23,\n                \"name\": \"Hebrew\",\n                \"order\": 0\n              },\n              {\n                \"value\": 27,\n                \"name\": \"Hindi\",\n                \"order\": 0\n              },\n              {\n                \"value\": 22,\n                \"name\": \"Hungarian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 9,\n                \"name\": \"Icelandic\",\n                \"order\": 0\n              },\n              {\n                \"value\": 44,\n                \"name\": \"Indonesian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 5,\n                \"name\": \"Italian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 8,\n                \"name\": \"Japanese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 21,\n                \"name\": \"Korean\",\n                \"order\": 0\n              },\n              {\n                \"value\": 36,\n                \"name\": \"Latvian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 24,\n                \"name\": \"Lithuanian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 45,\n                \"name\": \"Macedonian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 29,\n                \"name\": \"Malayalam\",\n                \"order\": 0\n              },\n              {\n                \"value\": 15,\n                \"name\": \"Norwegian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 37,\n                \"name\": \"Persian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 12,\n                \"name\": \"Polish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 18,\n                \"name\": \"Portuguese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 33,\n                \"name\": \"Portuguese (Brazil)\",\n                \"order\": 0\n              },\n              {\n                \"value\": 35,\n                \"name\": \"Romanian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 11,\n                \"name\": \"Russian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 40,\n                \"name\": \"Serbian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 31,\n                \"name\": \"Slovak\",\n                \"order\": 0\n              },\n              {\n                \"value\": 46,\n                \"name\": \"Slovenian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Spanish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 34,\n                \"name\": \"Spanish (Latino)\",\n                \"order\": 0\n              },\n              {\n                \"value\": 14,\n                \"name\": \"Swedish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 43,\n                \"name\": \"Tamil\",\n                \"order\": 0\n              },\n              {\n                \"value\": 32,\n                \"name\": \"Thai\",\n                \"order\": 0\n              },\n              {\n                \"value\": 17,\n                \"name\": \"Turkish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 30,\n                \"name\": \"Ukrainian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 13,\n                \"name\": \"Vietnamese\",\n                \"order\": 0\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not English\",\n        \"implementation\": \"LanguageSpecification\",\n        \"implementationName\": \"Language\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Language\",\n            \"value\": 1,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": -2,\n                \"name\": \"Original\",\n                \"order\": 0\n              },\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 26,\n                \"name\": \"Arabic\",\n                \"order\": 0\n              },\n              {\n                \"value\": 41,\n                \"name\": \"Bosnian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 28,\n                \"name\": \"Bulgarian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 38,\n                \"name\": \"Catalan\",\n                \"order\": 0\n              },\n              {\n                \"value\": 10,\n                \"name\": \"Chinese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 39,\n                \"name\": \"Croatian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 25,\n                \"name\": \"Czech\",\n                \"order\": 0\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Danish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 7,\n                \"name\": \"Dutch\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"English\",\n                \"order\": 0\n              },\n              {\n                \"value\": 42,\n                \"name\": \"Estonian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 16,\n                \"name\": \"Finnish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 19,\n                \"name\": \"Flemish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 2,\n                \"name\": \"French\",\n                \"order\": 0\n              },\n              {\n                \"value\": 4,\n                \"name\": \"German\",\n                \"order\": 0\n              },\n              {\n                \"value\": 20,\n                \"name\": \"Greek\",\n                \"order\": 0\n              },\n              {\n                \"value\": 23,\n                \"name\": \"Hebrew\",\n                \"order\": 0\n              },\n              {\n                \"value\": 27,\n                \"name\": \"Hindi\",\n                \"order\": 0\n              },\n              {\n                \"value\": 22,\n                \"name\": \"Hungarian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 9,\n                \"name\": \"Icelandic\",\n                \"order\": 0\n              },\n              {\n                \"value\": 44,\n                \"name\": \"Indonesian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 5,\n                \"name\": \"Italian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 8,\n                \"name\": \"Japanese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 21,\n                \"name\": \"Korean\",\n                \"order\": 0\n              },\n              {\n                \"value\": 36,\n                \"name\": \"Latvian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 24,\n                \"name\": \"Lithuanian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 45,\n                \"name\": \"Macedonian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 29,\n                \"name\": \"Malayalam\",\n                \"order\": 0\n              },\n              {\n                \"value\": 15,\n                \"name\": \"Norwegian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 37,\n                \"name\": \"Persian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 12,\n                \"name\": \"Polish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 18,\n                \"name\": \"Portuguese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 33,\n                \"name\": \"Portuguese (Brazil)\",\n                \"order\": 0\n              },\n              {\n                \"value\": 35,\n                \"name\": \"Romanian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 11,\n                \"name\": \"Russian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 40,\n                \"name\": \"Serbian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 31,\n                \"name\": \"Slovak\",\n                \"order\": 0\n              },\n              {\n                \"value\": 46,\n                \"name\": \"Slovenian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Spanish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 34,\n                \"name\": \"Spanish (Latino)\",\n                \"order\": 0\n              },\n              {\n                \"value\": 14,\n                \"name\": \"Swedish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 43,\n                \"name\": \"Tamil\",\n                \"order\": 0\n              },\n              {\n                \"value\": 32,\n                \"name\": \"Thai\",\n                \"order\": 0\n              },\n              {\n                \"value\": 17,\n                \"name\": \"Turkish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 30,\n                \"name\": \"Ukrainian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 13,\n                \"name\": \"Vietnamese\",\n                \"order\": 0\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 95,\n    \"name\": \"Language: Prefer German\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"German Language\",\n        \"implementation\": \"LanguageSpecification\",\n        \"implementationName\": \"Language\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Language\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": -2,\n                \"name\": \"Original\",\n                \"order\": 0\n              },\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 26,\n                \"name\": \"Arabic\",\n                \"order\": 0\n              },\n              {\n                \"value\": 41,\n                \"name\": \"Bosnian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 28,\n                \"name\": \"Bulgarian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 38,\n                \"name\": \"Catalan\",\n                \"order\": 0\n              },\n              {\n                \"value\": 10,\n                \"name\": \"Chinese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 39,\n                \"name\": \"Croatian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 25,\n                \"name\": \"Czech\",\n                \"order\": 0\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Danish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 7,\n                \"name\": \"Dutch\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"English\",\n                \"order\": 0\n              },\n              {\n                \"value\": 42,\n                \"name\": \"Estonian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 16,\n                \"name\": \"Finnish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 19,\n                \"name\": \"Flemish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 2,\n                \"name\": \"French\",\n                \"order\": 0\n              },\n              {\n                \"value\": 4,\n                \"name\": \"German\",\n                \"order\": 0\n              },\n              {\n                \"value\": 20,\n                \"name\": \"Greek\",\n                \"order\": 0\n              },\n              {\n                \"value\": 23,\n                \"name\": \"Hebrew\",\n                \"order\": 0\n              },\n              {\n                \"value\": 27,\n                \"name\": \"Hindi\",\n                \"order\": 0\n              },\n              {\n                \"value\": 22,\n                \"name\": \"Hungarian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 9,\n                \"name\": \"Icelandic\",\n                \"order\": 0\n              },\n              {\n                \"value\": 44,\n                \"name\": \"Indonesian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 5,\n                \"name\": \"Italian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 8,\n                \"name\": \"Japanese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 21,\n                \"name\": \"Korean\",\n                \"order\": 0\n              },\n              {\n                \"value\": 36,\n                \"name\": \"Latvian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 24,\n                \"name\": \"Lithuanian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 45,\n                \"name\": \"Macedonian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 29,\n                \"name\": \"Malayalam\",\n                \"order\": 0\n              },\n              {\n                \"value\": 15,\n                \"name\": \"Norwegian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 37,\n                \"name\": \"Persian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 12,\n                \"name\": \"Polish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 18,\n                \"name\": \"Portuguese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 33,\n                \"name\": \"Portuguese (Brazil)\",\n                \"order\": 0\n              },\n              {\n                \"value\": 35,\n                \"name\": \"Romanian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 11,\n                \"name\": \"Russian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 40,\n                \"name\": \"Serbian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 31,\n                \"name\": \"Slovak\",\n                \"order\": 0\n              },\n              {\n                \"value\": 46,\n                \"name\": \"Slovenian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Spanish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 34,\n                \"name\": \"Spanish (Latino)\",\n                \"order\": 0\n              },\n              {\n                \"value\": 14,\n                \"name\": \"Swedish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 43,\n                \"name\": \"Tamil\",\n                \"order\": 0\n              },\n              {\n                \"value\": 32,\n                \"name\": \"Thai\",\n                \"order\": 0\n              },\n              {\n                \"value\": 17,\n                \"name\": \"Turkish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 30,\n                \"name\": \"Ukrainian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 13,\n                \"name\": \"Vietnamese\",\n                \"order\": 0\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 96,\n    \"name\": \"x265 (no HDR/DV)\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"x265/HEVC\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"[xh][ ._-]?265|\\\\bHEVC(\\\\b|\\\\d)\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not HDR/DV\",\n        \"implementation\": \"ReleaseTitleSpecification\",\n        \"implementationName\": \"Release Title\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"\\\\b(dv|dovi|dolby[ .]?v(ision)?|hdr(10(P(lus)?)?)?|pq)\\\\b\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Not 2160p\",\n        \"implementation\": \"ResolutionSpecification\",\n        \"implementationName\": \"Resolution\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": true,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Resolution\",\n            \"value\": 2160,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 360,\n                \"name\": \"R360P\",\n                \"order\": 360\n              },\n              {\n                \"value\": 480,\n                \"name\": \"R480P\",\n                \"order\": 480\n              },\n              {\n                \"value\": 540,\n                \"name\": \"R540p\",\n                \"order\": 540\n              },\n              {\n                \"value\": 576,\n                \"name\": \"R576p\",\n                \"order\": 576\n              },\n              {\n                \"value\": 720,\n                \"name\": \"R720p\",\n                \"order\": 720\n              },\n              {\n                \"value\": 1080,\n                \"name\": \"R1080p\",\n                \"order\": 1080\n              },\n              {\n                \"value\": 2160,\n                \"name\": \"R2160p\",\n                \"order\": 2160\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 97,\n    \"name\": \"Language: Not German\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Not German Language\",\n        \"implementation\": \"LanguageSpecification\",\n        \"implementationName\": \"Language\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": true,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Language\",\n            \"value\": 4,\n            \"type\": \"select\",\n            \"advanced\": false,\n            \"selectOptions\": [\n              {\n                \"value\": -2,\n                \"name\": \"Original\",\n                \"order\": 0\n              },\n              {\n                \"value\": 0,\n                \"name\": \"Unknown\",\n                \"order\": 0\n              },\n              {\n                \"value\": 26,\n                \"name\": \"Arabic\",\n                \"order\": 0\n              },\n              {\n                \"value\": 41,\n                \"name\": \"Bosnian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 28,\n                \"name\": \"Bulgarian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 38,\n                \"name\": \"Catalan\",\n                \"order\": 0\n              },\n              {\n                \"value\": 10,\n                \"name\": \"Chinese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 39,\n                \"name\": \"Croatian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 25,\n                \"name\": \"Czech\",\n                \"order\": 0\n              },\n              {\n                \"value\": 6,\n                \"name\": \"Danish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 7,\n                \"name\": \"Dutch\",\n                \"order\": 0\n              },\n              {\n                \"value\": 1,\n                \"name\": \"English\",\n                \"order\": 0\n              },\n              {\n                \"value\": 42,\n                \"name\": \"Estonian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 16,\n                \"name\": \"Finnish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 19,\n                \"name\": \"Flemish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 2,\n                \"name\": \"French\",\n                \"order\": 0\n              },\n              {\n                \"value\": 4,\n                \"name\": \"German\",\n                \"order\": 0\n              },\n              {\n                \"value\": 20,\n                \"name\": \"Greek\",\n                \"order\": 0\n              },\n              {\n                \"value\": 23,\n                \"name\": \"Hebrew\",\n                \"order\": 0\n              },\n              {\n                \"value\": 27,\n                \"name\": \"Hindi\",\n                \"order\": 0\n              },\n              {\n                \"value\": 22,\n                \"name\": \"Hungarian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 9,\n                \"name\": \"Icelandic\",\n                \"order\": 0\n              },\n              {\n                \"value\": 44,\n                \"name\": \"Indonesian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 5,\n                \"name\": \"Italian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 8,\n                \"name\": \"Japanese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 21,\n                \"name\": \"Korean\",\n                \"order\": 0\n              },\n              {\n                \"value\": 36,\n                \"name\": \"Latvian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 24,\n                \"name\": \"Lithuanian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 45,\n                \"name\": \"Macedonian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 29,\n                \"name\": \"Malayalam\",\n                \"order\": 0\n              },\n              {\n                \"value\": 15,\n                \"name\": \"Norwegian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 37,\n                \"name\": \"Persian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 12,\n                \"name\": \"Polish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 18,\n                \"name\": \"Portuguese\",\n                \"order\": 0\n              },\n              {\n                \"value\": 33,\n                \"name\": \"Portuguese (Brazil)\",\n                \"order\": 0\n              },\n              {\n                \"value\": 35,\n                \"name\": \"Romanian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 11,\n                \"name\": \"Russian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 40,\n                \"name\": \"Serbian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 31,\n                \"name\": \"Slovak\",\n                \"order\": 0\n              },\n              {\n                \"value\": 46,\n                \"name\": \"Slovenian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 3,\n                \"name\": \"Spanish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 34,\n                \"name\": \"Spanish (Latino)\",\n                \"order\": 0\n              },\n              {\n                \"value\": 14,\n                \"name\": \"Swedish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 43,\n                \"name\": \"Tamil\",\n                \"order\": 0\n              },\n              {\n                \"value\": 32,\n                \"name\": \"Thai\",\n                \"order\": 0\n              },\n              {\n                \"value\": 17,\n                \"name\": \"Turkish\",\n                \"order\": 0\n              },\n              {\n                \"value\": 30,\n                \"name\": \"Ukrainian\",\n                \"order\": 0\n              },\n              {\n                \"value\": 13,\n                \"name\": \"Vietnamese\",\n                \"order\": 0\n              }\n            ],\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 98,\n    \"name\": \"GER HD Rel. Group Tier 01\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"ZeroTwo\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"ZeroTwo\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WAYNE\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"WAYNE\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TSCC\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"TSCC\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DETAiLS\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"DETAiLS\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"FoST\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"FoST\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SAUERKRAUT\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"SAUERKRAUT\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"Baka (Anime)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"Baka\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"D02KU\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"D02KU\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WvF\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"WvF\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 99,\n    \"name\": \"GER HD Rel. Group Tier 02\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"iNTENTiON\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"iNTENTiON\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TVS (keine ENG subs?)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"TVS\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TV4A (check ob Tier 01)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"TV4A\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TvR\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"TvR\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TVARCHiV\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"TVARCHiV\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"RUBBiSH\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"RUBBiSH\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MGE\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"MGE\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HAXE\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"HAXE\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"RSG\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"RSG\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"AWARDS (Inactive)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"AWARDS\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TMSF\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"TMSF\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"HQC (Inactive)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"HQC\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"DMPD\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"DMPD\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"MiSFiTS (Inactive)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"MiSFiTS\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"4SJ (Inactive)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"4SJ\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"euHD (Inactive)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"euHD\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"FuN (Low Size 265)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"FuN\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"OCA (Inactive)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"OCA\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"JaJunge\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"JaJunge\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"RWP\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"RWP\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"WOTT\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"WOTT\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 100,\n    \"name\": \"GER HD Rel. Group Tier 03\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"AIDA\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"AIDA\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"SunDry\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"SunDry\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"TVP (Inactive)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"TVP\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"UTOPiA\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"UTOPiA\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"VECTOR (Evaluate 07.2024)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"VECTOR\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"EXCiTED (Inactive)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"EXCiTED\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      },\n      {\n        \"name\": \"FRAGGERS (Inactive)\",\n        \"implementation\": \"ReleaseGroupSpecification\",\n        \"implementationName\": \"Release Group\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"value\",\n            \"label\": \"Regular Expression\",\n            \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n            \"value\": \"FRAGGERS\",\n            \"type\": \"textbox\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": false\n          }\n        ]\n      }\n    ]\n  },\n  {\n    \"id\": 103,\n    \"name\": \"Size: Block More 40GB\",\n    \"includeCustomFormatWhenRenaming\": false,\n    \"specifications\": [\n      {\n        \"name\": \"Size\",\n        \"implementation\": \"SizeSpecification\",\n        \"implementationName\": \"Size\",\n        \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n        \"negate\": false,\n        \"required\": false,\n        \"fields\": [\n          {\n            \"order\": 0,\n            \"name\": \"min\",\n            \"label\": \"Minimum Size\",\n            \"unit\": \"GB\",\n            \"helpText\": \"Release must be greater than this size\",\n            \"value\": 1,\n            \"type\": \"number\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": true\n          },\n          {\n            \"order\": 1,\n            \"name\": \"max\",\n            \"label\": \"Maximum Size\",\n            \"unit\": \"GB\",\n            \"helpText\": \"Release must be less than or equal to this size\",\n            \"value\": 9,\n            \"type\": \"number\",\n            \"advanced\": false,\n            \"privacy\": \"normal\",\n            \"isFloat\": true\n          }\n        ]\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "tests/samples/qualityDefinition.json",
    "content": "[\n  {\n    \"quality\": {\n      \"id\": 0,\n      \"name\": \"Unknown\",\n      \"source\": \"unknown\",\n      \"resolution\": 0\n    },\n    \"title\": \"Unknown\",\n    \"weight\": 1,\n    \"minSize\": 1,\n    \"maxSize\": 199.9,\n    \"preferredSize\": 194.9,\n    \"id\": 1\n  },\n  {\n    \"quality\": {\n      \"id\": 1,\n      \"name\": \"SDTV\",\n      \"source\": \"television\",\n      \"resolution\": 480\n    },\n    \"title\": \"SDTV\",\n    \"weight\": 2,\n    \"minSize\": 2,\n    \"maxSize\": 100,\n    \"preferredSize\": 95,\n    \"id\": 2\n  },\n  {\n    \"quality\": {\n      \"id\": 12,\n      \"name\": \"WEBRip-480p\",\n      \"source\": \"webRip\",\n      \"resolution\": 480\n    },\n    \"title\": \"WEBRip-480p\",\n    \"weight\": 3,\n    \"minSize\": 2,\n    \"maxSize\": 100,\n    \"preferredSize\": 95,\n    \"id\": 3\n  },\n  {\n    \"quality\": {\n      \"id\": 8,\n      \"name\": \"WEBDL-480p\",\n      \"source\": \"web\",\n      \"resolution\": 480\n    },\n    \"title\": \"WEBDL-480p\",\n    \"weight\": 3,\n    \"minSize\": 2,\n    \"maxSize\": 100,\n    \"preferredSize\": 95,\n    \"id\": 4\n  },\n  {\n    \"quality\": {\n      \"id\": 2,\n      \"name\": \"DVD\",\n      \"source\": \"dvd\",\n      \"resolution\": 480\n    },\n    \"title\": \"DVD\",\n    \"weight\": 4,\n    \"minSize\": 2,\n    \"maxSize\": 100,\n    \"preferredSize\": 95,\n    \"id\": 5\n  },\n  {\n    \"quality\": {\n      \"id\": 13,\n      \"name\": \"Bluray-480p\",\n      \"source\": \"bluray\",\n      \"resolution\": 480\n    },\n    \"title\": \"Bluray-480p\",\n    \"weight\": 5,\n    \"minSize\": 2,\n    \"maxSize\": 100,\n    \"preferredSize\": 95,\n    \"id\": 6\n  },\n  {\n    \"quality\": {\n      \"id\": 4,\n      \"name\": \"HDTV-720p\",\n      \"source\": \"television\",\n      \"resolution\": 720\n    },\n    \"title\": \"HDTV-720p\",\n    \"weight\": 6,\n    \"minSize\": 10,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 7\n  },\n  {\n    \"quality\": {\n      \"id\": 9,\n      \"name\": \"HDTV-1080p\",\n      \"source\": \"television\",\n      \"resolution\": 1080\n    },\n    \"title\": \"HDTV-1080p\",\n    \"weight\": 7,\n    \"minSize\": 15,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 8\n  },\n  {\n    \"quality\": {\n      \"id\": 10,\n      \"name\": \"Raw-HD\",\n      \"source\": \"televisionRaw\",\n      \"resolution\": 1080\n    },\n    \"title\": \"Raw-HD\",\n    \"weight\": 8,\n    \"minSize\": 4,\n    \"id\": 9\n  },\n  {\n    \"quality\": {\n      \"id\": 14,\n      \"name\": \"WEBRip-720p\",\n      \"source\": \"webRip\",\n      \"resolution\": 720\n    },\n    \"title\": \"WEBRip-720p\",\n    \"weight\": 9,\n    \"minSize\": 10,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 10\n  },\n  {\n    \"quality\": {\n      \"id\": 5,\n      \"name\": \"WEBDL-720p\",\n      \"source\": \"web\",\n      \"resolution\": 720\n    },\n    \"title\": \"WEBDL-720p\",\n    \"weight\": 9,\n    \"minSize\": 10,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 11\n  },\n  {\n    \"quality\": {\n      \"id\": 6,\n      \"name\": \"Bluray-720p\",\n      \"source\": \"bluray\",\n      \"resolution\": 720\n    },\n    \"title\": \"Bluray-720p\",\n    \"weight\": 10,\n    \"minSize\": 17.1,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 12\n  },\n  {\n    \"quality\": {\n      \"id\": 15,\n      \"name\": \"WEBRip-1080p\",\n      \"source\": \"webRip\",\n      \"resolution\": 1080\n    },\n    \"title\": \"WEBRip-1080p\",\n    \"weight\": 11,\n    \"minSize\": 15,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 13\n  },\n  {\n    \"quality\": {\n      \"id\": 3,\n      \"name\": \"WEBDL-1080p\",\n      \"source\": \"web\",\n      \"resolution\": 1080\n    },\n    \"title\": \"WEBDL-1080p\",\n    \"weight\": 11,\n    \"minSize\": 15,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 14\n  },\n  {\n    \"quality\": {\n      \"id\": 7,\n      \"name\": \"Bluray-1080p\",\n      \"source\": \"bluray\",\n      \"resolution\": 1080\n    },\n    \"title\": \"Bluray-1080p\",\n    \"weight\": 12,\n    \"minSize\": 50.4,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 15\n  },\n  {\n    \"quality\": {\n      \"id\": 20,\n      \"name\": \"Bluray-1080p Remux\",\n      \"source\": \"blurayRaw\",\n      \"resolution\": 1080\n    },\n    \"title\": \"Bluray-1080p Remux\",\n    \"weight\": 13,\n    \"minSize\": 69.1,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 16\n  },\n  {\n    \"quality\": {\n      \"id\": 16,\n      \"name\": \"HDTV-2160p\",\n      \"source\": \"television\",\n      \"resolution\": 2160\n    },\n    \"title\": \"HDTV-2160p\",\n    \"weight\": 14,\n    \"minSize\": 25,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 17\n  },\n  {\n    \"quality\": {\n      \"id\": 17,\n      \"name\": \"WEBRip-2160p\",\n      \"source\": \"webRip\",\n      \"resolution\": 2160\n    },\n    \"title\": \"WEBRip-2160p\",\n    \"weight\": 15,\n    \"minSize\": 25,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 18\n  },\n  {\n    \"quality\": {\n      \"id\": 18,\n      \"name\": \"WEBDL-2160p\",\n      \"source\": \"web\",\n      \"resolution\": 2160\n    },\n    \"title\": \"WEBDL-2160p\",\n    \"weight\": 15,\n    \"minSize\": 25,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 19\n  },\n  {\n    \"quality\": {\n      \"id\": 19,\n      \"name\": \"Bluray-2160p\",\n      \"source\": \"bluray\",\n      \"resolution\": 2160\n    },\n    \"title\": \"Bluray-2160p\",\n    \"weight\": 16,\n    \"minSize\": 94.6,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 20\n  },\n  {\n    \"quality\": {\n      \"id\": 21,\n      \"name\": \"Bluray-2160p Remux\",\n      \"source\": \"blurayRaw\",\n      \"resolution\": 2160\n    },\n    \"title\": \"Bluray-2160p Remux\",\n    \"weight\": 17,\n    \"minSize\": 187.4,\n    \"maxSize\": 400,\n    \"preferredSize\": 399,\n    \"id\": 21\n  }\n]\n"
  },
  {
    "path": "tests/samples/quality_profiles.json",
    "content": "[\n  {\n    \"name\": \"WEB-2160p\",\n    \"upgradeAllowed\": true,\n    \"cutoff\": 1003,\n    \"items\": [\n      {\n        \"quality\": {\n          \"id\": 0,\n          \"name\": \"Unknown\",\n          \"source\": \"unknown\",\n          \"resolution\": 0\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 1,\n          \"name\": \"SDTV\",\n          \"source\": \"television\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 480p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 12,\n              \"name\": \"WEBRip-480p\",\n              \"source\": \"webRip\",\n              \"resolution\": 480\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 8,\n              \"name\": \"WEBDL-480p\",\n              \"source\": \"web\",\n              \"resolution\": 480\n            },\n            \"items\": [],\n            \"allowed\": false\n          }\n        ],\n        \"allowed\": false,\n        \"id\": 1000\n      },\n      {\n        \"quality\": {\n          \"id\": 2,\n          \"name\": \"DVD\",\n          \"source\": \"dvd\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 13,\n          \"name\": \"Bluray-480p\",\n          \"source\": \"bluray\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 10,\n          \"name\": \"Raw-HD\",\n          \"source\": \"televisionRaw\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 6,\n          \"name\": \"Bluray-720p\",\n          \"source\": \"bluray\",\n          \"resolution\": 720\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 7,\n          \"name\": \"Bluray-1080p\",\n          \"source\": \"bluray\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 20,\n          \"name\": \"Bluray-1080p Remux\",\n          \"source\": \"blurayRaw\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 19,\n          \"name\": \"Bluray-2160p\",\n          \"source\": \"bluray\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 21,\n          \"name\": \"Bluray-2160p Remux\",\n          \"source\": \"blurayRaw\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 720p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 4,\n              \"name\": \"HDTV-720p\",\n              \"source\": \"television\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 14,\n              \"name\": \"WEBRip-720p\",\n              \"source\": \"webRip\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 5,\n              \"name\": \"WEBDL-720p\",\n              \"source\": \"web\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": false\n          }\n        ],\n        \"allowed\": false,\n        \"id\": 1001\n      },\n      {\n        \"name\": \"WEB 1080p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 9,\n              \"name\": \"HDTV-1080p\",\n              \"source\": \"television\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 15,\n              \"name\": \"WEBRip-1080p\",\n              \"source\": \"webRip\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 3,\n              \"name\": \"WEBDL-1080p\",\n              \"source\": \"web\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": false\n          }\n        ],\n        \"allowed\": false,\n        \"id\": 1002\n      },\n      {\n        \"quality\": {\n          \"id\": 16,\n          \"name\": \"HDTV-2160p\",\n          \"source\": \"television\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 2160p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 17,\n              \"name\": \"WEBRip-2160p\",\n              \"source\": \"webRip\",\n              \"resolution\": 2160\n            },\n            \"items\": [],\n            \"allowed\": true\n          },\n          {\n            \"quality\": {\n              \"id\": 18,\n              \"name\": \"WEBDL-2160p\",\n              \"source\": \"web\",\n              \"resolution\": 2160\n            },\n            \"items\": [],\n            \"allowed\": true\n          }\n        ],\n        \"allowed\": true,\n        \"id\": 1003\n      }\n    ],\n    \"minFormatScore\": 0,\n    \"cutoffFormatScore\": 1000,\n    \"formatItems\": [\n      {\n        \"format\": 103,\n        \"name\": \"Size: Block More 40GB\",\n        \"score\": 0\n      },\n      {\n        \"format\": 100,\n        \"name\": \"GER HD Rel. Group Tier 03\",\n        \"score\": 0\n      },\n      {\n        \"format\": 99,\n        \"name\": \"GER HD Rel. Group Tier 02\",\n        \"score\": 0\n      },\n      {\n        \"format\": 98,\n        \"name\": \"GER HD Rel. Group Tier 01\",\n        \"score\": 0\n      },\n      {\n        \"format\": 41,\n        \"name\": \"DV HDR10+\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 42,\n        \"name\": \"DV HDR10\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 43,\n        \"name\": \"DV HLG\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 44,\n        \"name\": \"DV SDR\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 45,\n        \"name\": \"DV\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 46,\n        \"name\": \"HDR (undefined)\",\n        \"score\": 500\n      },\n      {\n        \"format\": 47,\n        \"name\": \"HDR\",\n        \"score\": 500\n      },\n      {\n        \"format\": 48,\n        \"name\": \"HDR10\",\n        \"score\": 500\n      },\n      {\n        \"format\": 49,\n        \"name\": \"HDR10+\",\n        \"score\": 600\n      },\n      {\n        \"format\": 50,\n        \"name\": \"HLG\",\n        \"score\": 500\n      },\n      {\n        \"format\": 51,\n        \"name\": \"PQ\",\n        \"score\": 500\n      },\n      {\n        \"format\": 1,\n        \"name\": \"BR-DISK\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 2,\n        \"name\": \"LQ\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 3,\n        \"name\": \"LQ (Release Title)\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 5,\n        \"name\": \"Extras\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 6,\n        \"name\": \"Repack/Proper\",\n        \"score\": 5\n      },\n      {\n        \"format\": 7,\n        \"name\": \"Repack v2\",\n        \"score\": 6\n      },\n      {\n        \"format\": 8,\n        \"name\": \"Repack v3\",\n        \"score\": 7\n      },\n      {\n        \"format\": 9,\n        \"name\": \"4OD\",\n        \"score\": 50\n      },\n      {\n        \"format\": 10,\n        \"name\": \"ALL4\",\n        \"score\": 50\n      },\n      {\n        \"format\": 11,\n        \"name\": \"AMZN\",\n        \"score\": 70\n      },\n      {\n        \"format\": 12,\n        \"name\": \"ATVP\",\n        \"score\": 100\n      },\n      {\n        \"format\": 13,\n        \"name\": \"CC\",\n        \"score\": 50\n      },\n      {\n        \"format\": 14,\n        \"name\": \"CRAV\",\n        \"score\": 50\n      },\n      {\n        \"format\": 15,\n        \"name\": \"DCU\",\n        \"score\": 50\n      },\n      {\n        \"format\": 16,\n        \"name\": \"DSNP\",\n        \"score\": 100\n      },\n      {\n        \"format\": 17,\n        \"name\": \"FOD\",\n        \"score\": 50\n      },\n      {\n        \"format\": 18,\n        \"name\": \"HBO\",\n        \"score\": 50\n      },\n      {\n        \"format\": 19,\n        \"name\": \"HMAX\",\n        \"score\": 80\n      },\n      {\n        \"format\": 20,\n        \"name\": \"HULU\",\n        \"score\": 50\n      },\n      {\n        \"format\": 21,\n        \"name\": \"IP\",\n        \"score\": 50\n      },\n      {\n        \"format\": 22,\n        \"name\": \"iT\",\n        \"score\": 50\n      },\n      {\n        \"format\": 23,\n        \"name\": \"MAX\",\n        \"score\": 90\n      },\n      {\n        \"format\": 24,\n        \"name\": \"NF\",\n        \"score\": 60\n      },\n      {\n        \"format\": 25,\n        \"name\": \"NLZ\",\n        \"score\": 50\n      },\n      {\n        \"format\": 26,\n        \"name\": \"OViD\",\n        \"score\": 50\n      },\n      {\n        \"format\": 27,\n        \"name\": \"PCOK\",\n        \"score\": 60\n      },\n      {\n        \"format\": 28,\n        \"name\": \"PMTP\",\n        \"score\": 60\n      },\n      {\n        \"format\": 29,\n        \"name\": \"QIBI\",\n        \"score\": 80\n      },\n      {\n        \"format\": 30,\n        \"name\": \"RED\",\n        \"score\": 50\n      },\n      {\n        \"format\": 31,\n        \"name\": \"SHO\",\n        \"score\": 50\n      },\n      {\n        \"format\": 32,\n        \"name\": \"STAN\",\n        \"score\": 60\n      },\n      {\n        \"format\": 33,\n        \"name\": \"TVer\",\n        \"score\": 50\n      },\n      {\n        \"format\": 34,\n        \"name\": \"U-NEXT\",\n        \"score\": 50\n      },\n      {\n        \"format\": 35,\n        \"name\": \"VDL\",\n        \"score\": 50\n      },\n      {\n        \"format\": 52,\n        \"name\": \"UHD Streaming Boost\",\n        \"score\": 20\n      },\n      {\n        \"format\": 53,\n        \"name\": \"UHD Streaming Cut\",\n        \"score\": -20\n      },\n      {\n        \"format\": 36,\n        \"name\": \"WEB Tier 01\",\n        \"score\": 1700\n      },\n      {\n        \"format\": 37,\n        \"name\": \"WEB Tier 02\",\n        \"score\": 1650\n      },\n      {\n        \"format\": 38,\n        \"name\": \"WEB Tier 03\",\n        \"score\": 1600\n      },\n      {\n        \"format\": 39,\n        \"name\": \"WEB Scene\",\n        \"score\": 1600\n      },\n      {\n        \"format\": 4,\n        \"name\": \"x265 (HD)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 96,\n        \"name\": \"x265 (no HDR/DV)\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 89,\n        \"name\": \"SDR\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 94,\n        \"name\": \"Language: Not German or English\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 95,\n        \"name\": \"Language: Prefer German\",\n        \"score\": 10\n      },\n      {\n        \"format\": 97,\n        \"name\": \"Language: Not German\",\n        \"score\": 0\n      },\n      {\n        \"format\": 88,\n        \"name\": \"DV (WEBDL)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 54,\n        \"name\": \"Anime BD Tier 01 (Top SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 55,\n        \"name\": \"Anime BD Tier 02 (SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 56,\n        \"name\": \"Anime BD Tier 03 (SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 57,\n        \"name\": \"Anime BD Tier 04 (SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 58,\n        \"name\": \"Anime BD Tier 05 (Remuxes)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 59,\n        \"name\": \"Anime BD Tier 06 (FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 60,\n        \"name\": \"Anime BD Tier 07 (P2P/Scene)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 61,\n        \"name\": \"Anime BD Tier 08 (Mini Encodes)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 62,\n        \"name\": \"Anime Web Tier 01 (Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 63,\n        \"name\": \"Anime Web Tier 02 (Top FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 64,\n        \"name\": \"Anime Web Tier 03 (Official Subs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 65,\n        \"name\": \"Anime Web Tier 04 (Official Subs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 66,\n        \"name\": \"Anime Web Tier 05 (FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 67,\n        \"name\": \"Anime Web Tier 06 (FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 68,\n        \"name\": \"Anime LQ Groups\",\n        \"score\": 0\n      },\n      {\n        \"format\": 69,\n        \"name\": \"Anime Raws\",\n        \"score\": 0\n      },\n      {\n        \"format\": 70,\n        \"name\": \"Dubs Only\",\n        \"score\": 0\n      },\n      {\n        \"format\": 71,\n        \"name\": \"v0\",\n        \"score\": 0\n      },\n      {\n        \"format\": 72,\n        \"name\": \"v1\",\n        \"score\": 0\n      },\n      {\n        \"format\": 73,\n        \"name\": \"v2\",\n        \"score\": 0\n      },\n      {\n        \"format\": 74,\n        \"name\": \"v3\",\n        \"score\": 0\n      },\n      {\n        \"format\": 75,\n        \"name\": \"v4\",\n        \"score\": 0\n      },\n      {\n        \"format\": 76,\n        \"name\": \"AV1\",\n        \"score\": 0\n      },\n      {\n        \"format\": 77,\n        \"name\": \"VOSTFR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 78,\n        \"name\": \"CR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 79,\n        \"name\": \"FUNi\",\n        \"score\": 0\n      },\n      {\n        \"format\": 80,\n        \"name\": \"VRV\",\n        \"score\": 0\n      },\n      {\n        \"format\": 81,\n        \"name\": \"ADN\",\n        \"score\": 0\n      },\n      {\n        \"format\": 82,\n        \"name\": \"B-Global\",\n        \"score\": 0\n      },\n      {\n        \"format\": 83,\n        \"name\": \"Bilibili\",\n        \"score\": 0\n      },\n      {\n        \"format\": 84,\n        \"name\": \"HIDIVE\",\n        \"score\": 0\n      },\n      {\n        \"format\": 85,\n        \"name\": \"ABEMA\",\n        \"score\": 0\n      },\n      {\n        \"format\": 86,\n        \"name\": \"Remux Tier 01\",\n        \"score\": 0\n      },\n      {\n        \"format\": 87,\n        \"name\": \"Remux Tier 02\",\n        \"score\": 0\n      },\n      {\n        \"format\": 90,\n        \"name\": \"Uncensored\",\n        \"score\": 0\n      },\n      {\n        \"format\": 91,\n        \"name\": \"10bit\",\n        \"score\": 0\n      },\n      {\n        \"format\": 92,\n        \"name\": \"Anime Dual Audio\",\n        \"score\": 0\n      }\n    ],\n    \"id\": 14\n  },\n  {\n    \"name\": \"Remux-1080p - Anime\",\n    \"upgradeAllowed\": true,\n    \"cutoff\": 1004,\n    \"items\": [\n      {\n        \"quality\": {\n          \"id\": 0,\n          \"name\": \"Unknown\",\n          \"source\": \"unknown\",\n          \"resolution\": 0\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 10,\n          \"name\": \"Raw-HD\",\n          \"source\": \"televisionRaw\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 16,\n          \"name\": \"HDTV-2160p\",\n          \"source\": \"television\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 2160p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 17,\n              \"name\": \"WEBRip-2160p\",\n              \"source\": \"webRip\",\n              \"resolution\": 2160\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 18,\n              \"name\": \"WEBDL-2160p\",\n              \"source\": \"web\",\n              \"resolution\": 2160\n            },\n            \"items\": [],\n            \"allowed\": false\n          }\n        ],\n        \"allowed\": false,\n        \"id\": 1003\n      },\n      {\n        \"quality\": {\n          \"id\": 19,\n          \"name\": \"Bluray-2160p\",\n          \"source\": \"bluray\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 21,\n          \"name\": \"Bluray-2160p Remux\",\n          \"source\": \"blurayRaw\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 1,\n          \"name\": \"SDTV\",\n          \"source\": \"television\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": true\n      },\n      {\n        \"quality\": {\n          \"id\": 2,\n          \"name\": \"DVD\",\n          \"source\": \"dvd\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": true\n      },\n      {\n        \"name\": \"WEB 480p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 12,\n              \"name\": \"WEBRip-480p\",\n              \"source\": \"webRip\",\n              \"resolution\": 480\n            },\n            \"items\": [],\n            \"allowed\": true\n          },\n          {\n            \"quality\": {\n              \"id\": 8,\n              \"name\": \"WEBDL-480p\",\n              \"source\": \"web\",\n              \"resolution\": 480\n            },\n            \"items\": [],\n            \"allowed\": true\n          }\n        ],\n        \"allowed\": true,\n        \"id\": 1000\n      },\n      {\n        \"quality\": {\n          \"id\": 13,\n          \"name\": \"Bluray-480p\",\n          \"source\": \"bluray\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": true\n      },\n      {\n        \"name\": \"WEB 720p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 4,\n              \"name\": \"HDTV-720p\",\n              \"source\": \"television\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": true\n          },\n          {\n            \"quality\": {\n              \"id\": 14,\n              \"name\": \"WEBRip-720p\",\n              \"source\": \"webRip\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": true\n          },\n          {\n            \"quality\": {\n              \"id\": 5,\n              \"name\": \"WEBDL-720p\",\n              \"source\": \"web\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": true\n          }\n        ],\n        \"allowed\": true,\n        \"id\": 1001\n      },\n      {\n        \"quality\": {\n          \"id\": 6,\n          \"name\": \"Bluray-720p\",\n          \"source\": \"bluray\",\n          \"resolution\": 720\n        },\n        \"items\": [],\n        \"allowed\": true\n      },\n      {\n        \"name\": \"WEB 1080p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 9,\n              \"name\": \"HDTV-1080p\",\n              \"source\": \"television\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": true\n          },\n          {\n            \"quality\": {\n              \"id\": 15,\n              \"name\": \"WEBRip-1080p\",\n              \"source\": \"webRip\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": true\n          },\n          {\n            \"quality\": {\n              \"id\": 3,\n              \"name\": \"WEBDL-1080p\",\n              \"source\": \"web\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": true\n          }\n        ],\n        \"allowed\": true,\n        \"id\": 1002\n      },\n      {\n        \"name\": \"Bluray-1080p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 7,\n              \"name\": \"Bluray-1080p\",\n              \"source\": \"bluray\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": true\n          },\n          {\n            \"quality\": {\n              \"id\": 20,\n              \"name\": \"Bluray-1080p Remux\",\n              \"source\": \"blurayRaw\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": true\n          }\n        ],\n        \"allowed\": true,\n        \"id\": 1004\n      }\n    ],\n    \"minFormatScore\": 100,\n    \"cutoffFormatScore\": 10000,\n    \"formatItems\": [\n      {\n        \"format\": 103,\n        \"name\": \"Size: Block More 40GB\",\n        \"score\": 0\n      },\n      {\n        \"format\": 100,\n        \"name\": \"GER HD Rel. Group Tier 03\",\n        \"score\": 0\n      },\n      {\n        \"format\": 99,\n        \"name\": \"GER HD Rel. Group Tier 02\",\n        \"score\": 0\n      },\n      {\n        \"format\": 98,\n        \"name\": \"GER HD Rel. Group Tier 01\",\n        \"score\": 0\n      },\n      {\n        \"format\": 97,\n        \"name\": \"Language: Not German\",\n        \"score\": 0\n      },\n      {\n        \"format\": 96,\n        \"name\": \"x265 (no HDR/DV)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 95,\n        \"name\": \"Language: Prefer German\",\n        \"score\": 0\n      },\n      {\n        \"format\": 94,\n        \"name\": \"Language: Not German or English\",\n        \"score\": 0\n      },\n      {\n        \"format\": 54,\n        \"name\": \"Anime BD Tier 01 (Top SeaDex Muxers)\",\n        \"score\": 1400\n      },\n      {\n        \"format\": 55,\n        \"name\": \"Anime BD Tier 02 (SeaDex Muxers)\",\n        \"score\": 1300\n      },\n      {\n        \"format\": 56,\n        \"name\": \"Anime BD Tier 03 (SeaDex Muxers)\",\n        \"score\": 1200\n      },\n      {\n        \"format\": 57,\n        \"name\": \"Anime BD Tier 04 (SeaDex Muxers)\",\n        \"score\": 1100\n      },\n      {\n        \"format\": 58,\n        \"name\": \"Anime BD Tier 05 (Remuxes)\",\n        \"score\": 1000\n      },\n      {\n        \"format\": 59,\n        \"name\": \"Anime BD Tier 06 (FanSubs)\",\n        \"score\": 900\n      },\n      {\n        \"format\": 60,\n        \"name\": \"Anime BD Tier 07 (P2P/Scene)\",\n        \"score\": 800\n      },\n      {\n        \"format\": 61,\n        \"name\": \"Anime BD Tier 08 (Mini Encodes)\",\n        \"score\": 700\n      },\n      {\n        \"format\": 62,\n        \"name\": \"Anime Web Tier 01 (Muxers)\",\n        \"score\": 600\n      },\n      {\n        \"format\": 63,\n        \"name\": \"Anime Web Tier 02 (Top FanSubs)\",\n        \"score\": 500\n      },\n      {\n        \"format\": 64,\n        \"name\": \"Anime Web Tier 03 (Official Subs)\",\n        \"score\": 400\n      },\n      {\n        \"format\": 65,\n        \"name\": \"Anime Web Tier 04 (Official Subs)\",\n        \"score\": 300\n      },\n      {\n        \"format\": 66,\n        \"name\": \"Anime Web Tier 05 (FanSubs)\",\n        \"score\": 200\n      },\n      {\n        \"format\": 67,\n        \"name\": \"Anime Web Tier 06 (FanSubs)\",\n        \"score\": 100\n      },\n      {\n        \"format\": 68,\n        \"name\": \"Anime LQ Groups\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 69,\n        \"name\": \"Anime Raws\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 70,\n        \"name\": \"Dubs Only\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 71,\n        \"name\": \"v0\",\n        \"score\": -51\n      },\n      {\n        \"format\": 72,\n        \"name\": \"v1\",\n        \"score\": 1\n      },\n      {\n        \"format\": 73,\n        \"name\": \"v2\",\n        \"score\": 2\n      },\n      {\n        \"format\": 74,\n        \"name\": \"v3\",\n        \"score\": 3\n      },\n      {\n        \"format\": 75,\n        \"name\": \"v4\",\n        \"score\": 4\n      },\n      {\n        \"format\": 76,\n        \"name\": \"AV1\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 77,\n        \"name\": \"VOSTFR\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 78,\n        \"name\": \"CR\",\n        \"score\": 6\n      },\n      {\n        \"format\": 79,\n        \"name\": \"FUNi\",\n        \"score\": 2\n      },\n      {\n        \"format\": 80,\n        \"name\": \"VRV\",\n        \"score\": 3\n      },\n      {\n        \"format\": 16,\n        \"name\": \"DSNP\",\n        \"score\": 5\n      },\n      {\n        \"format\": 24,\n        \"name\": \"NF\",\n        \"score\": 4\n      },\n      {\n        \"format\": 11,\n        \"name\": \"AMZN\",\n        \"score\": 3\n      },\n      {\n        \"format\": 81,\n        \"name\": \"ADN\",\n        \"score\": 1\n      },\n      {\n        \"format\": 85,\n        \"name\": \"ABEMA\",\n        \"score\": 1\n      },\n      {\n        \"format\": 86,\n        \"name\": \"Remux Tier 01\",\n        \"score\": 1050\n      },\n      {\n        \"format\": 87,\n        \"name\": \"Remux Tier 02\",\n        \"score\": 1000\n      },\n      {\n        \"format\": 36,\n        \"name\": \"WEB Tier 01\",\n        \"score\": 350\n      },\n      {\n        \"format\": 37,\n        \"name\": \"WEB Tier 02\",\n        \"score\": 150\n      },\n      {\n        \"format\": 38,\n        \"name\": \"WEB Tier 03\",\n        \"score\": 150\n      },\n      {\n        \"format\": 90,\n        \"name\": \"Uncensored\",\n        \"score\": 0\n      },\n      {\n        \"format\": 91,\n        \"name\": \"10bit\",\n        \"score\": 0\n      },\n      {\n        \"format\": 92,\n        \"name\": \"Anime Dual Audio\",\n        \"score\": 0\n      },\n      {\n        \"format\": 1,\n        \"name\": \"BR-DISK\",\n        \"score\": 0\n      },\n      {\n        \"format\": 2,\n        \"name\": \"LQ\",\n        \"score\": 0\n      },\n      {\n        \"format\": 3,\n        \"name\": \"LQ (Release Title)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 4,\n        \"name\": \"x265 (HD)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 5,\n        \"name\": \"Extras\",\n        \"score\": 0\n      },\n      {\n        \"format\": 6,\n        \"name\": \"Repack/Proper\",\n        \"score\": 0\n      },\n      {\n        \"format\": 7,\n        \"name\": \"Repack v2\",\n        \"score\": 0\n      },\n      {\n        \"format\": 8,\n        \"name\": \"Repack v3\",\n        \"score\": 0\n      },\n      {\n        \"format\": 9,\n        \"name\": \"4OD\",\n        \"score\": 0\n      },\n      {\n        \"format\": 10,\n        \"name\": \"ALL4\",\n        \"score\": 0\n      },\n      {\n        \"format\": 12,\n        \"name\": \"ATVP\",\n        \"score\": 0\n      },\n      {\n        \"format\": 13,\n        \"name\": \"CC\",\n        \"score\": 0\n      },\n      {\n        \"format\": 14,\n        \"name\": \"CRAV\",\n        \"score\": 0\n      },\n      {\n        \"format\": 15,\n        \"name\": \"DCU\",\n        \"score\": 0\n      },\n      {\n        \"format\": 17,\n        \"name\": \"FOD\",\n        \"score\": 0\n      },\n      {\n        \"format\": 18,\n        \"name\": \"HBO\",\n        \"score\": 0\n      },\n      {\n        \"format\": 19,\n        \"name\": \"HMAX\",\n        \"score\": 0\n      },\n      {\n        \"format\": 20,\n        \"name\": \"HULU\",\n        \"score\": 0\n      },\n      {\n        \"format\": 21,\n        \"name\": \"IP\",\n        \"score\": 0\n      },\n      {\n        \"format\": 22,\n        \"name\": \"iT\",\n        \"score\": 0\n      },\n      {\n        \"format\": 23,\n        \"name\": \"MAX\",\n        \"score\": 0\n      },\n      {\n        \"format\": 25,\n        \"name\": \"NLZ\",\n        \"score\": 0\n      },\n      {\n        \"format\": 26,\n        \"name\": \"OViD\",\n        \"score\": 0\n      },\n      {\n        \"format\": 27,\n        \"name\": \"PCOK\",\n        \"score\": 0\n      },\n      {\n        \"format\": 28,\n        \"name\": \"PMTP\",\n        \"score\": 0\n      },\n      {\n        \"format\": 29,\n        \"name\": \"QIBI\",\n        \"score\": 0\n      },\n      {\n        \"format\": 30,\n        \"name\": \"RED\",\n        \"score\": 0\n      },\n      {\n        \"format\": 31,\n        \"name\": \"SHO\",\n        \"score\": 0\n      },\n      {\n        \"format\": 32,\n        \"name\": \"STAN\",\n        \"score\": 0\n      },\n      {\n        \"format\": 33,\n        \"name\": \"TVer\",\n        \"score\": 0\n      },\n      {\n        \"format\": 34,\n        \"name\": \"U-NEXT\",\n        \"score\": 0\n      },\n      {\n        \"format\": 35,\n        \"name\": \"VDL\",\n        \"score\": 0\n      },\n      {\n        \"format\": 39,\n        \"name\": \"WEB Scene\",\n        \"score\": 0\n      },\n      {\n        \"format\": 41,\n        \"name\": \"DV HDR10+\",\n        \"score\": 0\n      },\n      {\n        \"format\": 42,\n        \"name\": \"DV HDR10\",\n        \"score\": 0\n      },\n      {\n        \"format\": 43,\n        \"name\": \"DV HLG\",\n        \"score\": 0\n      },\n      {\n        \"format\": 44,\n        \"name\": \"DV SDR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 45,\n        \"name\": \"DV\",\n        \"score\": 0\n      },\n      {\n        \"format\": 46,\n        \"name\": \"HDR (undefined)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 47,\n        \"name\": \"HDR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 48,\n        \"name\": \"HDR10\",\n        \"score\": 0\n      },\n      {\n        \"format\": 49,\n        \"name\": \"HDR10+\",\n        \"score\": 0\n      },\n      {\n        \"format\": 50,\n        \"name\": \"HLG\",\n        \"score\": 0\n      },\n      {\n        \"format\": 51,\n        \"name\": \"PQ\",\n        \"score\": 0\n      },\n      {\n        \"format\": 52,\n        \"name\": \"UHD Streaming Boost\",\n        \"score\": 0\n      },\n      {\n        \"format\": 53,\n        \"name\": \"UHD Streaming Cut\",\n        \"score\": 0\n      },\n      {\n        \"format\": 82,\n        \"name\": \"B-Global\",\n        \"score\": 0\n      },\n      {\n        \"format\": 83,\n        \"name\": \"Bilibili\",\n        \"score\": 0\n      },\n      {\n        \"format\": 84,\n        \"name\": \"HIDIVE\",\n        \"score\": 0\n      },\n      {\n        \"format\": 88,\n        \"name\": \"DV (WEBDL)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 89,\n        \"name\": \"SDR\",\n        \"score\": 0\n      }\n    ],\n    \"id\": 15\n  },\n  {\n    \"name\": \"WEB-1080p\",\n    \"upgradeAllowed\": true,\n    \"cutoff\": 1002,\n    \"items\": [\n      {\n        \"quality\": {\n          \"id\": 0,\n          \"name\": \"Unknown\",\n          \"source\": \"unknown\",\n          \"resolution\": 0\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 1,\n          \"name\": \"SDTV\",\n          \"source\": \"television\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 480p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 12,\n              \"name\": \"WEBRip-480p\",\n              \"source\": \"webRip\",\n              \"resolution\": 480\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 8,\n              \"name\": \"WEBDL-480p\",\n              \"source\": \"web\",\n              \"resolution\": 480\n            },\n            \"items\": [],\n            \"allowed\": false\n          }\n        ],\n        \"allowed\": false,\n        \"id\": 1000\n      },\n      {\n        \"quality\": {\n          \"id\": 2,\n          \"name\": \"DVD\",\n          \"source\": \"dvd\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 13,\n          \"name\": \"Bluray-480p\",\n          \"source\": \"bluray\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 10,\n          \"name\": \"Raw-HD\",\n          \"source\": \"televisionRaw\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 6,\n          \"name\": \"Bluray-720p\",\n          \"source\": \"bluray\",\n          \"resolution\": 720\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 7,\n          \"name\": \"Bluray-1080p\",\n          \"source\": \"bluray\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 20,\n          \"name\": \"Bluray-1080p Remux\",\n          \"source\": \"blurayRaw\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 16,\n          \"name\": \"HDTV-2160p\",\n          \"source\": \"television\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 2160p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 17,\n              \"name\": \"WEBRip-2160p\",\n              \"source\": \"webRip\",\n              \"resolution\": 2160\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 18,\n              \"name\": \"WEBDL-2160p\",\n              \"source\": \"web\",\n              \"resolution\": 2160\n            },\n            \"items\": [],\n            \"allowed\": false\n          }\n        ],\n        \"allowed\": false,\n        \"id\": 1003\n      },\n      {\n        \"quality\": {\n          \"id\": 19,\n          \"name\": \"Bluray-2160p\",\n          \"source\": \"bluray\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 21,\n          \"name\": \"Bluray-2160p Remux\",\n          \"source\": \"blurayRaw\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 720p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 4,\n              \"name\": \"HDTV-720p\",\n              \"source\": \"television\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 14,\n              \"name\": \"WEBRip-720p\",\n              \"source\": \"webRip\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 5,\n              \"name\": \"WEBDL-720p\",\n              \"source\": \"web\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": false\n          }\n        ],\n        \"allowed\": false,\n        \"id\": 1001\n      },\n      {\n        \"quality\": {\n          \"id\": 9,\n          \"name\": \"HDTV-1080p\",\n          \"source\": \"television\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 1080p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 15,\n              \"name\": \"WEBRip-1080p\",\n              \"source\": \"webRip\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": true\n          },\n          {\n            \"quality\": {\n              \"id\": 3,\n              \"name\": \"WEBDL-1080p\",\n              \"source\": \"web\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": true\n          }\n        ],\n        \"allowed\": true,\n        \"id\": 1002\n      }\n    ],\n    \"minFormatScore\": 0,\n    \"cutoffFormatScore\": 1000,\n    \"formatItems\": [\n      {\n        \"format\": 103,\n        \"name\": \"Size: Block More 40GB\",\n        \"score\": 0\n      },\n      {\n        \"format\": 100,\n        \"name\": \"GER HD Rel. Group Tier 03\",\n        \"score\": 0\n      },\n      {\n        \"format\": 99,\n        \"name\": \"GER HD Rel. Group Tier 02\",\n        \"score\": 0\n      },\n      {\n        \"format\": 98,\n        \"name\": \"GER HD Rel. Group Tier 01\",\n        \"score\": 0\n      },\n      {\n        \"format\": 1,\n        \"name\": \"BR-DISK\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 2,\n        \"name\": \"LQ\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 3,\n        \"name\": \"LQ (Release Title)\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 5,\n        \"name\": \"Extras\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 6,\n        \"name\": \"Repack/Proper\",\n        \"score\": 5\n      },\n      {\n        \"format\": 7,\n        \"name\": \"Repack v2\",\n        \"score\": 6\n      },\n      {\n        \"format\": 8,\n        \"name\": \"Repack v3\",\n        \"score\": 7\n      },\n      {\n        \"format\": 9,\n        \"name\": \"4OD\",\n        \"score\": 50\n      },\n      {\n        \"format\": 10,\n        \"name\": \"ALL4\",\n        \"score\": 50\n      },\n      {\n        \"format\": 11,\n        \"name\": \"AMZN\",\n        \"score\": 70\n      },\n      {\n        \"format\": 12,\n        \"name\": \"ATVP\",\n        \"score\": 100\n      },\n      {\n        \"format\": 13,\n        \"name\": \"CC\",\n        \"score\": 50\n      },\n      {\n        \"format\": 14,\n        \"name\": \"CRAV\",\n        \"score\": 50\n      },\n      {\n        \"format\": 15,\n        \"name\": \"DCU\",\n        \"score\": 50\n      },\n      {\n        \"format\": 16,\n        \"name\": \"DSNP\",\n        \"score\": 100\n      },\n      {\n        \"format\": 17,\n        \"name\": \"FOD\",\n        \"score\": 50\n      },\n      {\n        \"format\": 18,\n        \"name\": \"HBO\",\n        \"score\": 50\n      },\n      {\n        \"format\": 19,\n        \"name\": \"HMAX\",\n        \"score\": 80\n      },\n      {\n        \"format\": 20,\n        \"name\": \"HULU\",\n        \"score\": 50\n      },\n      {\n        \"format\": 21,\n        \"name\": \"IP\",\n        \"score\": 50\n      },\n      {\n        \"format\": 22,\n        \"name\": \"iT\",\n        \"score\": 50\n      },\n      {\n        \"format\": 23,\n        \"name\": \"MAX\",\n        \"score\": 90\n      },\n      {\n        \"format\": 24,\n        \"name\": \"NF\",\n        \"score\": 60\n      },\n      {\n        \"format\": 25,\n        \"name\": \"NLZ\",\n        \"score\": 50\n      },\n      {\n        \"format\": 26,\n        \"name\": \"OViD\",\n        \"score\": 50\n      },\n      {\n        \"format\": 27,\n        \"name\": \"PCOK\",\n        \"score\": 60\n      },\n      {\n        \"format\": 28,\n        \"name\": \"PMTP\",\n        \"score\": 60\n      },\n      {\n        \"format\": 29,\n        \"name\": \"QIBI\",\n        \"score\": 80\n      },\n      {\n        \"format\": 30,\n        \"name\": \"RED\",\n        \"score\": 50\n      },\n      {\n        \"format\": 31,\n        \"name\": \"SHO\",\n        \"score\": 50\n      },\n      {\n        \"format\": 32,\n        \"name\": \"STAN\",\n        \"score\": 60\n      },\n      {\n        \"format\": 33,\n        \"name\": \"TVer\",\n        \"score\": 50\n      },\n      {\n        \"format\": 34,\n        \"name\": \"U-NEXT\",\n        \"score\": 50\n      },\n      {\n        \"format\": 35,\n        \"name\": \"VDL\",\n        \"score\": 50\n      },\n      {\n        \"format\": 36,\n        \"name\": \"WEB Tier 01\",\n        \"score\": 1700\n      },\n      {\n        \"format\": 37,\n        \"name\": \"WEB Tier 02\",\n        \"score\": 1650\n      },\n      {\n        \"format\": 38,\n        \"name\": \"WEB Tier 03\",\n        \"score\": 1600\n      },\n      {\n        \"format\": 39,\n        \"name\": \"WEB Scene\",\n        \"score\": 1600\n      },\n      {\n        \"format\": 4,\n        \"name\": \"x265 (HD)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 96,\n        \"name\": \"x265 (no HDR/DV)\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 49,\n        \"name\": \"HDR10+\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 41,\n        \"name\": \"DV HDR10+\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 94,\n        \"name\": \"Language: Not German or English\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 95,\n        \"name\": \"Language: Prefer German\",\n        \"score\": 10\n      },\n      {\n        \"format\": 97,\n        \"name\": \"Language: Not German\",\n        \"score\": 0\n      },\n      {\n        \"format\": 42,\n        \"name\": \"DV HDR10\",\n        \"score\": 0\n      },\n      {\n        \"format\": 43,\n        \"name\": \"DV HLG\",\n        \"score\": 0\n      },\n      {\n        \"format\": 44,\n        \"name\": \"DV SDR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 45,\n        \"name\": \"DV\",\n        \"score\": 0\n      },\n      {\n        \"format\": 46,\n        \"name\": \"HDR (undefined)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 47,\n        \"name\": \"HDR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 48,\n        \"name\": \"HDR10\",\n        \"score\": 0\n      },\n      {\n        \"format\": 50,\n        \"name\": \"HLG\",\n        \"score\": 0\n      },\n      {\n        \"format\": 51,\n        \"name\": \"PQ\",\n        \"score\": 0\n      },\n      {\n        \"format\": 52,\n        \"name\": \"UHD Streaming Boost\",\n        \"score\": 0\n      },\n      {\n        \"format\": 53,\n        \"name\": \"UHD Streaming Cut\",\n        \"score\": 0\n      },\n      {\n        \"format\": 54,\n        \"name\": \"Anime BD Tier 01 (Top SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 55,\n        \"name\": \"Anime BD Tier 02 (SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 56,\n        \"name\": \"Anime BD Tier 03 (SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 57,\n        \"name\": \"Anime BD Tier 04 (SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 58,\n        \"name\": \"Anime BD Tier 05 (Remuxes)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 59,\n        \"name\": \"Anime BD Tier 06 (FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 60,\n        \"name\": \"Anime BD Tier 07 (P2P/Scene)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 61,\n        \"name\": \"Anime BD Tier 08 (Mini Encodes)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 62,\n        \"name\": \"Anime Web Tier 01 (Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 63,\n        \"name\": \"Anime Web Tier 02 (Top FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 64,\n        \"name\": \"Anime Web Tier 03 (Official Subs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 65,\n        \"name\": \"Anime Web Tier 04 (Official Subs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 66,\n        \"name\": \"Anime Web Tier 05 (FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 67,\n        \"name\": \"Anime Web Tier 06 (FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 68,\n        \"name\": \"Anime LQ Groups\",\n        \"score\": 0\n      },\n      {\n        \"format\": 69,\n        \"name\": \"Anime Raws\",\n        \"score\": 0\n      },\n      {\n        \"format\": 70,\n        \"name\": \"Dubs Only\",\n        \"score\": 0\n      },\n      {\n        \"format\": 71,\n        \"name\": \"v0\",\n        \"score\": 0\n      },\n      {\n        \"format\": 72,\n        \"name\": \"v1\",\n        \"score\": 0\n      },\n      {\n        \"format\": 73,\n        \"name\": \"v2\",\n        \"score\": 0\n      },\n      {\n        \"format\": 74,\n        \"name\": \"v3\",\n        \"score\": 0\n      },\n      {\n        \"format\": 75,\n        \"name\": \"v4\",\n        \"score\": 0\n      },\n      {\n        \"format\": 76,\n        \"name\": \"AV1\",\n        \"score\": 0\n      },\n      {\n        \"format\": 77,\n        \"name\": \"VOSTFR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 78,\n        \"name\": \"CR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 79,\n        \"name\": \"FUNi\",\n        \"score\": 0\n      },\n      {\n        \"format\": 80,\n        \"name\": \"VRV\",\n        \"score\": 0\n      },\n      {\n        \"format\": 81,\n        \"name\": \"ADN\",\n        \"score\": 0\n      },\n      {\n        \"format\": 82,\n        \"name\": \"B-Global\",\n        \"score\": 0\n      },\n      {\n        \"format\": 83,\n        \"name\": \"Bilibili\",\n        \"score\": 0\n      },\n      {\n        \"format\": 84,\n        \"name\": \"HIDIVE\",\n        \"score\": 0\n      },\n      {\n        \"format\": 85,\n        \"name\": \"ABEMA\",\n        \"score\": 0\n      },\n      {\n        \"format\": 86,\n        \"name\": \"Remux Tier 01\",\n        \"score\": 0\n      },\n      {\n        \"format\": 87,\n        \"name\": \"Remux Tier 02\",\n        \"score\": 0\n      },\n      {\n        \"format\": 88,\n        \"name\": \"DV (WEBDL)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 89,\n        \"name\": \"SDR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 90,\n        \"name\": \"Uncensored\",\n        \"score\": 0\n      },\n      {\n        \"format\": 91,\n        \"name\": \"10bit\",\n        \"score\": 0\n      },\n      {\n        \"format\": 92,\n        \"name\": \"Anime Dual Audio\",\n        \"score\": 0\n      }\n    ],\n    \"id\": 17\n  },\n  {\n    \"name\": \"Default (DE/EN)\",\n    \"upgradeAllowed\": true,\n    \"cutoff\": 1003,\n    \"items\": [\n      {\n        \"quality\": {\n          \"id\": 0,\n          \"name\": \"Unknown\",\n          \"source\": \"unknown\",\n          \"resolution\": 0\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 1,\n          \"name\": \"SDTV\",\n          \"source\": \"television\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 480p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 12,\n              \"name\": \"WEBRip-480p\",\n              \"source\": \"webRip\",\n              \"resolution\": 480\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 8,\n              \"name\": \"WEBDL-480p\",\n              \"source\": \"web\",\n              \"resolution\": 480\n            },\n            \"items\": [],\n            \"allowed\": false\n          }\n        ],\n        \"allowed\": false,\n        \"id\": 1000\n      },\n      {\n        \"quality\": {\n          \"id\": 2,\n          \"name\": \"DVD\",\n          \"source\": \"dvd\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 13,\n          \"name\": \"Bluray-480p\",\n          \"source\": \"bluray\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 4,\n          \"name\": \"HDTV-720p\",\n          \"source\": \"television\",\n          \"resolution\": 720\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 9,\n          \"name\": \"HDTV-1080p\",\n          \"source\": \"television\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 10,\n          \"name\": \"Raw-HD\",\n          \"source\": \"televisionRaw\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 720p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 14,\n              \"name\": \"WEBRip-720p\",\n              \"source\": \"webRip\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 5,\n              \"name\": \"WEBDL-720p\",\n              \"source\": \"web\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": false\n          }\n        ],\n        \"allowed\": false,\n        \"id\": 1001\n      },\n      {\n        \"quality\": {\n          \"id\": 6,\n          \"name\": \"Bluray-720p\",\n          \"source\": \"bluray\",\n          \"resolution\": 720\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 7,\n          \"name\": \"Bluray-1080p\",\n          \"source\": \"bluray\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 20,\n          \"name\": \"Bluray-1080p Remux\",\n          \"source\": \"blurayRaw\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 16,\n          \"name\": \"HDTV-2160p\",\n          \"source\": \"television\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 19,\n          \"name\": \"Bluray-2160p\",\n          \"source\": \"bluray\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 21,\n          \"name\": \"Bluray-2160p Remux\",\n          \"source\": \"blurayRaw\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 1080p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 15,\n              \"name\": \"WEBRip-1080p\",\n              \"source\": \"webRip\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": true\n          },\n          {\n            \"quality\": {\n              \"id\": 3,\n              \"name\": \"WEBDL-1080p\",\n              \"source\": \"web\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": true\n          }\n        ],\n        \"allowed\": true,\n        \"id\": 1002\n      },\n      {\n        \"name\": \"WEB 2160p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 17,\n              \"name\": \"WEBRip-2160p\",\n              \"source\": \"webRip\",\n              \"resolution\": 2160\n            },\n            \"items\": [],\n            \"allowed\": true\n          },\n          {\n            \"quality\": {\n              \"id\": 18,\n              \"name\": \"WEBDL-2160p\",\n              \"source\": \"web\",\n              \"resolution\": 2160\n            },\n            \"items\": [],\n            \"allowed\": true\n          }\n        ],\n        \"allowed\": true,\n        \"id\": 1003\n      }\n    ],\n    \"minFormatScore\": 0,\n    \"cutoffFormatScore\": 1000,\n    \"formatItems\": [\n      {\n        \"format\": 103,\n        \"name\": \"Size: Block More 40GB\",\n        \"score\": 0\n      },\n      {\n        \"format\": 100,\n        \"name\": \"GER HD Rel. Group Tier 03\",\n        \"score\": 0\n      },\n      {\n        \"format\": 99,\n        \"name\": \"GER HD Rel. Group Tier 02\",\n        \"score\": 0\n      },\n      {\n        \"format\": 98,\n        \"name\": \"GER HD Rel. Group Tier 01\",\n        \"score\": 0\n      },\n      {\n        \"format\": 42,\n        \"name\": \"DV HDR10\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 43,\n        \"name\": \"DV HLG\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 44,\n        \"name\": \"DV SDR\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 45,\n        \"name\": \"DV\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 46,\n        \"name\": \"HDR (undefined)\",\n        \"score\": 500\n      },\n      {\n        \"format\": 47,\n        \"name\": \"HDR\",\n        \"score\": 500\n      },\n      {\n        \"format\": 48,\n        \"name\": \"HDR10\",\n        \"score\": 500\n      },\n      {\n        \"format\": 50,\n        \"name\": \"HLG\",\n        \"score\": 500\n      },\n      {\n        \"format\": 51,\n        \"name\": \"PQ\",\n        \"score\": 500\n      },\n      {\n        \"format\": 1,\n        \"name\": \"BR-DISK\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 2,\n        \"name\": \"LQ\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 3,\n        \"name\": \"LQ (Release Title)\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 5,\n        \"name\": \"Extras\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 6,\n        \"name\": \"Repack/Proper\",\n        \"score\": 5\n      },\n      {\n        \"format\": 7,\n        \"name\": \"Repack v2\",\n        \"score\": 6\n      },\n      {\n        \"format\": 8,\n        \"name\": \"Repack v3\",\n        \"score\": 7\n      },\n      {\n        \"format\": 9,\n        \"name\": \"4OD\",\n        \"score\": 50\n      },\n      {\n        \"format\": 10,\n        \"name\": \"ALL4\",\n        \"score\": 50\n      },\n      {\n        \"format\": 11,\n        \"name\": \"AMZN\",\n        \"score\": 70\n      },\n      {\n        \"format\": 12,\n        \"name\": \"ATVP\",\n        \"score\": 100\n      },\n      {\n        \"format\": 13,\n        \"name\": \"CC\",\n        \"score\": 50\n      },\n      {\n        \"format\": 14,\n        \"name\": \"CRAV\",\n        \"score\": 50\n      },\n      {\n        \"format\": 15,\n        \"name\": \"DCU\",\n        \"score\": 50\n      },\n      {\n        \"format\": 16,\n        \"name\": \"DSNP\",\n        \"score\": 100\n      },\n      {\n        \"format\": 17,\n        \"name\": \"FOD\",\n        \"score\": 50\n      },\n      {\n        \"format\": 18,\n        \"name\": \"HBO\",\n        \"score\": 50\n      },\n      {\n        \"format\": 19,\n        \"name\": \"HMAX\",\n        \"score\": 80\n      },\n      {\n        \"format\": 20,\n        \"name\": \"HULU\",\n        \"score\": 50\n      },\n      {\n        \"format\": 21,\n        \"name\": \"IP\",\n        \"score\": 50\n      },\n      {\n        \"format\": 22,\n        \"name\": \"iT\",\n        \"score\": 50\n      },\n      {\n        \"format\": 23,\n        \"name\": \"MAX\",\n        \"score\": 90\n      },\n      {\n        \"format\": 24,\n        \"name\": \"NF\",\n        \"score\": 60\n      },\n      {\n        \"format\": 25,\n        \"name\": \"NLZ\",\n        \"score\": 50\n      },\n      {\n        \"format\": 26,\n        \"name\": \"OViD\",\n        \"score\": 50\n      },\n      {\n        \"format\": 27,\n        \"name\": \"PCOK\",\n        \"score\": 60\n      },\n      {\n        \"format\": 28,\n        \"name\": \"PMTP\",\n        \"score\": 60\n      },\n      {\n        \"format\": 29,\n        \"name\": \"QIBI\",\n        \"score\": 80\n      },\n      {\n        \"format\": 30,\n        \"name\": \"RED\",\n        \"score\": 50\n      },\n      {\n        \"format\": 31,\n        \"name\": \"SHO\",\n        \"score\": 50\n      },\n      {\n        \"format\": 32,\n        \"name\": \"STAN\",\n        \"score\": 60\n      },\n      {\n        \"format\": 33,\n        \"name\": \"TVer\",\n        \"score\": 50\n      },\n      {\n        \"format\": 34,\n        \"name\": \"U-NEXT\",\n        \"score\": 50\n      },\n      {\n        \"format\": 35,\n        \"name\": \"VDL\",\n        \"score\": 50\n      },\n      {\n        \"format\": 52,\n        \"name\": \"UHD Streaming Boost\",\n        \"score\": 20\n      },\n      {\n        \"format\": 53,\n        \"name\": \"UHD Streaming Cut\",\n        \"score\": -20\n      },\n      {\n        \"format\": 36,\n        \"name\": \"WEB Tier 01\",\n        \"score\": 1700\n      },\n      {\n        \"format\": 37,\n        \"name\": \"WEB Tier 02\",\n        \"score\": 1650\n      },\n      {\n        \"format\": 38,\n        \"name\": \"WEB Tier 03\",\n        \"score\": 1600\n      },\n      {\n        \"format\": 39,\n        \"name\": \"WEB Scene\",\n        \"score\": 1600\n      },\n      {\n        \"format\": 94,\n        \"name\": \"Language: Not German or English\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 95,\n        \"name\": \"Language: Prefer German\",\n        \"score\": 10\n      },\n      {\n        \"format\": 4,\n        \"name\": \"x265 (HD)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 96,\n        \"name\": \"x265 (no HDR/DV)\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 49,\n        \"name\": \"HDR10+\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 41,\n        \"name\": \"DV HDR10+\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 89,\n        \"name\": \"SDR\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 54,\n        \"name\": \"Anime BD Tier 01 (Top SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 55,\n        \"name\": \"Anime BD Tier 02 (SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 56,\n        \"name\": \"Anime BD Tier 03 (SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 57,\n        \"name\": \"Anime BD Tier 04 (SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 58,\n        \"name\": \"Anime BD Tier 05 (Remuxes)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 59,\n        \"name\": \"Anime BD Tier 06 (FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 60,\n        \"name\": \"Anime BD Tier 07 (P2P/Scene)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 61,\n        \"name\": \"Anime BD Tier 08 (Mini Encodes)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 62,\n        \"name\": \"Anime Web Tier 01 (Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 63,\n        \"name\": \"Anime Web Tier 02 (Top FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 64,\n        \"name\": \"Anime Web Tier 03 (Official Subs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 65,\n        \"name\": \"Anime Web Tier 04 (Official Subs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 66,\n        \"name\": \"Anime Web Tier 05 (FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 67,\n        \"name\": \"Anime Web Tier 06 (FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 68,\n        \"name\": \"Anime LQ Groups\",\n        \"score\": 0\n      },\n      {\n        \"format\": 69,\n        \"name\": \"Anime Raws\",\n        \"score\": 0\n      },\n      {\n        \"format\": 70,\n        \"name\": \"Dubs Only\",\n        \"score\": 0\n      },\n      {\n        \"format\": 71,\n        \"name\": \"v0\",\n        \"score\": 0\n      },\n      {\n        \"format\": 72,\n        \"name\": \"v1\",\n        \"score\": 0\n      },\n      {\n        \"format\": 73,\n        \"name\": \"v2\",\n        \"score\": 0\n      },\n      {\n        \"format\": 74,\n        \"name\": \"v3\",\n        \"score\": 0\n      },\n      {\n        \"format\": 75,\n        \"name\": \"v4\",\n        \"score\": 0\n      },\n      {\n        \"format\": 76,\n        \"name\": \"AV1\",\n        \"score\": 0\n      },\n      {\n        \"format\": 77,\n        \"name\": \"VOSTFR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 78,\n        \"name\": \"CR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 79,\n        \"name\": \"FUNi\",\n        \"score\": 0\n      },\n      {\n        \"format\": 80,\n        \"name\": \"VRV\",\n        \"score\": 0\n      },\n      {\n        \"format\": 81,\n        \"name\": \"ADN\",\n        \"score\": 0\n      },\n      {\n        \"format\": 82,\n        \"name\": \"B-Global\",\n        \"score\": 0\n      },\n      {\n        \"format\": 83,\n        \"name\": \"Bilibili\",\n        \"score\": 0\n      },\n      {\n        \"format\": 84,\n        \"name\": \"HIDIVE\",\n        \"score\": 0\n      },\n      {\n        \"format\": 85,\n        \"name\": \"ABEMA\",\n        \"score\": 0\n      },\n      {\n        \"format\": 86,\n        \"name\": \"Remux Tier 01\",\n        \"score\": 0\n      },\n      {\n        \"format\": 87,\n        \"name\": \"Remux Tier 02\",\n        \"score\": 0\n      },\n      {\n        \"format\": 88,\n        \"name\": \"DV (WEBDL)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 90,\n        \"name\": \"Uncensored\",\n        \"score\": 0\n      },\n      {\n        \"format\": 91,\n        \"name\": \"10bit\",\n        \"score\": 0\n      },\n      {\n        \"format\": 92,\n        \"name\": \"Anime Dual Audio\",\n        \"score\": 0\n      },\n      {\n        \"format\": 97,\n        \"name\": \"Language: Not German\",\n        \"score\": 0\n      }\n    ],\n    \"id\": 19\n  },\n  {\n    \"name\": \"Default (DE)\",\n    \"upgradeAllowed\": true,\n    \"cutoff\": 1003,\n    \"items\": [\n      {\n        \"quality\": {\n          \"id\": 0,\n          \"name\": \"Unknown\",\n          \"source\": \"unknown\",\n          \"resolution\": 0\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 1,\n          \"name\": \"SDTV\",\n          \"source\": \"television\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 480p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 12,\n              \"name\": \"WEBRip-480p\",\n              \"source\": \"webRip\",\n              \"resolution\": 480\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 8,\n              \"name\": \"WEBDL-480p\",\n              \"source\": \"web\",\n              \"resolution\": 480\n            },\n            \"items\": [],\n            \"allowed\": false\n          }\n        ],\n        \"allowed\": false,\n        \"id\": 1000\n      },\n      {\n        \"quality\": {\n          \"id\": 2,\n          \"name\": \"DVD\",\n          \"source\": \"dvd\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 13,\n          \"name\": \"Bluray-480p\",\n          \"source\": \"bluray\",\n          \"resolution\": 480\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 4,\n          \"name\": \"HDTV-720p\",\n          \"source\": \"television\",\n          \"resolution\": 720\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 9,\n          \"name\": \"HDTV-1080p\",\n          \"source\": \"television\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 10,\n          \"name\": \"Raw-HD\",\n          \"source\": \"televisionRaw\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 720p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 14,\n              \"name\": \"WEBRip-720p\",\n              \"source\": \"webRip\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": false\n          },\n          {\n            \"quality\": {\n              \"id\": 5,\n              \"name\": \"WEBDL-720p\",\n              \"source\": \"web\",\n              \"resolution\": 720\n            },\n            \"items\": [],\n            \"allowed\": false\n          }\n        ],\n        \"allowed\": false,\n        \"id\": 1001\n      },\n      {\n        \"quality\": {\n          \"id\": 6,\n          \"name\": \"Bluray-720p\",\n          \"source\": \"bluray\",\n          \"resolution\": 720\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 7,\n          \"name\": \"Bluray-1080p\",\n          \"source\": \"bluray\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 20,\n          \"name\": \"Bluray-1080p Remux\",\n          \"source\": \"blurayRaw\",\n          \"resolution\": 1080\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 16,\n          \"name\": \"HDTV-2160p\",\n          \"source\": \"television\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 19,\n          \"name\": \"Bluray-2160p\",\n          \"source\": \"bluray\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"quality\": {\n          \"id\": 21,\n          \"name\": \"Bluray-2160p Remux\",\n          \"source\": \"blurayRaw\",\n          \"resolution\": 2160\n        },\n        \"items\": [],\n        \"allowed\": false\n      },\n      {\n        \"name\": \"WEB 1080p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 15,\n              \"name\": \"WEBRip-1080p\",\n              \"source\": \"webRip\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": true\n          },\n          {\n            \"quality\": {\n              \"id\": 3,\n              \"name\": \"WEBDL-1080p\",\n              \"source\": \"web\",\n              \"resolution\": 1080\n            },\n            \"items\": [],\n            \"allowed\": true\n          }\n        ],\n        \"allowed\": true,\n        \"id\": 1002\n      },\n      {\n        \"name\": \"WEB 2160p\",\n        \"items\": [\n          {\n            \"quality\": {\n              \"id\": 17,\n              \"name\": \"WEBRip-2160p\",\n              \"source\": \"webRip\",\n              \"resolution\": 2160\n            },\n            \"items\": [],\n            \"allowed\": true\n          },\n          {\n            \"quality\": {\n              \"id\": 18,\n              \"name\": \"WEBDL-2160p\",\n              \"source\": \"web\",\n              \"resolution\": 2160\n            },\n            \"items\": [],\n            \"allowed\": true\n          }\n        ],\n        \"allowed\": true,\n        \"id\": 1003\n      }\n    ],\n    \"minFormatScore\": 0,\n    \"cutoffFormatScore\": 1000,\n    \"formatItems\": [\n      {\n        \"format\": 103,\n        \"name\": \"Size: Block More 40GB\",\n        \"score\": 0\n      },\n      {\n        \"format\": 100,\n        \"name\": \"GER HD Rel. Group Tier 03\",\n        \"score\": 0\n      },\n      {\n        \"format\": 99,\n        \"name\": \"GER HD Rel. Group Tier 02\",\n        \"score\": 0\n      },\n      {\n        \"format\": 98,\n        \"name\": \"GER HD Rel. Group Tier 01\",\n        \"score\": 0\n      },\n      {\n        \"format\": 42,\n        \"name\": \"DV HDR10\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 43,\n        \"name\": \"DV HLG\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 44,\n        \"name\": \"DV SDR\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 45,\n        \"name\": \"DV\",\n        \"score\": 1500\n      },\n      {\n        \"format\": 46,\n        \"name\": \"HDR (undefined)\",\n        \"score\": 500\n      },\n      {\n        \"format\": 47,\n        \"name\": \"HDR\",\n        \"score\": 500\n      },\n      {\n        \"format\": 48,\n        \"name\": \"HDR10\",\n        \"score\": 500\n      },\n      {\n        \"format\": 50,\n        \"name\": \"HLG\",\n        \"score\": 500\n      },\n      {\n        \"format\": 51,\n        \"name\": \"PQ\",\n        \"score\": 500\n      },\n      {\n        \"format\": 1,\n        \"name\": \"BR-DISK\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 2,\n        \"name\": \"LQ\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 3,\n        \"name\": \"LQ (Release Title)\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 5,\n        \"name\": \"Extras\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 6,\n        \"name\": \"Repack/Proper\",\n        \"score\": 5\n      },\n      {\n        \"format\": 7,\n        \"name\": \"Repack v2\",\n        \"score\": 6\n      },\n      {\n        \"format\": 8,\n        \"name\": \"Repack v3\",\n        \"score\": 7\n      },\n      {\n        \"format\": 9,\n        \"name\": \"4OD\",\n        \"score\": 50\n      },\n      {\n        \"format\": 10,\n        \"name\": \"ALL4\",\n        \"score\": 50\n      },\n      {\n        \"format\": 11,\n        \"name\": \"AMZN\",\n        \"score\": 70\n      },\n      {\n        \"format\": 12,\n        \"name\": \"ATVP\",\n        \"score\": 100\n      },\n      {\n        \"format\": 13,\n        \"name\": \"CC\",\n        \"score\": 50\n      },\n      {\n        \"format\": 14,\n        \"name\": \"CRAV\",\n        \"score\": 50\n      },\n      {\n        \"format\": 15,\n        \"name\": \"DCU\",\n        \"score\": 50\n      },\n      {\n        \"format\": 16,\n        \"name\": \"DSNP\",\n        \"score\": 100\n      },\n      {\n        \"format\": 17,\n        \"name\": \"FOD\",\n        \"score\": 50\n      },\n      {\n        \"format\": 18,\n        \"name\": \"HBO\",\n        \"score\": 50\n      },\n      {\n        \"format\": 19,\n        \"name\": \"HMAX\",\n        \"score\": 80\n      },\n      {\n        \"format\": 20,\n        \"name\": \"HULU\",\n        \"score\": 50\n      },\n      {\n        \"format\": 21,\n        \"name\": \"IP\",\n        \"score\": 50\n      },\n      {\n        \"format\": 22,\n        \"name\": \"iT\",\n        \"score\": 50\n      },\n      {\n        \"format\": 23,\n        \"name\": \"MAX\",\n        \"score\": 90\n      },\n      {\n        \"format\": 24,\n        \"name\": \"NF\",\n        \"score\": 60\n      },\n      {\n        \"format\": 25,\n        \"name\": \"NLZ\",\n        \"score\": 50\n      },\n      {\n        \"format\": 26,\n        \"name\": \"OViD\",\n        \"score\": 50\n      },\n      {\n        \"format\": 27,\n        \"name\": \"PCOK\",\n        \"score\": 60\n      },\n      {\n        \"format\": 28,\n        \"name\": \"PMTP\",\n        \"score\": 60\n      },\n      {\n        \"format\": 29,\n        \"name\": \"QIBI\",\n        \"score\": 80\n      },\n      {\n        \"format\": 30,\n        \"name\": \"RED\",\n        \"score\": 50\n      },\n      {\n        \"format\": 31,\n        \"name\": \"SHO\",\n        \"score\": 50\n      },\n      {\n        \"format\": 32,\n        \"name\": \"STAN\",\n        \"score\": 60\n      },\n      {\n        \"format\": 33,\n        \"name\": \"TVer\",\n        \"score\": 50\n      },\n      {\n        \"format\": 34,\n        \"name\": \"U-NEXT\",\n        \"score\": 50\n      },\n      {\n        \"format\": 35,\n        \"name\": \"VDL\",\n        \"score\": 50\n      },\n      {\n        \"format\": 52,\n        \"name\": \"UHD Streaming Boost\",\n        \"score\": 20\n      },\n      {\n        \"format\": 53,\n        \"name\": \"UHD Streaming Cut\",\n        \"score\": -20\n      },\n      {\n        \"format\": 36,\n        \"name\": \"WEB Tier 01\",\n        \"score\": 1700\n      },\n      {\n        \"format\": 37,\n        \"name\": \"WEB Tier 02\",\n        \"score\": 1650\n      },\n      {\n        \"format\": 38,\n        \"name\": \"WEB Tier 03\",\n        \"score\": 1600\n      },\n      {\n        \"format\": 39,\n        \"name\": \"WEB Scene\",\n        \"score\": 1600\n      },\n      {\n        \"format\": 97,\n        \"name\": \"Language: Not German\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 4,\n        \"name\": \"x265 (HD)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 96,\n        \"name\": \"x265 (no HDR/DV)\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 49,\n        \"name\": \"HDR10+\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 41,\n        \"name\": \"DV HDR10+\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 89,\n        \"name\": \"SDR\",\n        \"score\": -10000\n      },\n      {\n        \"format\": 54,\n        \"name\": \"Anime BD Tier 01 (Top SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 55,\n        \"name\": \"Anime BD Tier 02 (SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 56,\n        \"name\": \"Anime BD Tier 03 (SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 57,\n        \"name\": \"Anime BD Tier 04 (SeaDex Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 58,\n        \"name\": \"Anime BD Tier 05 (Remuxes)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 59,\n        \"name\": \"Anime BD Tier 06 (FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 60,\n        \"name\": \"Anime BD Tier 07 (P2P/Scene)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 61,\n        \"name\": \"Anime BD Tier 08 (Mini Encodes)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 62,\n        \"name\": \"Anime Web Tier 01 (Muxers)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 63,\n        \"name\": \"Anime Web Tier 02 (Top FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 64,\n        \"name\": \"Anime Web Tier 03 (Official Subs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 65,\n        \"name\": \"Anime Web Tier 04 (Official Subs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 66,\n        \"name\": \"Anime Web Tier 05 (FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 67,\n        \"name\": \"Anime Web Tier 06 (FanSubs)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 68,\n        \"name\": \"Anime LQ Groups\",\n        \"score\": 0\n      },\n      {\n        \"format\": 69,\n        \"name\": \"Anime Raws\",\n        \"score\": 0\n      },\n      {\n        \"format\": 70,\n        \"name\": \"Dubs Only\",\n        \"score\": 0\n      },\n      {\n        \"format\": 71,\n        \"name\": \"v0\",\n        \"score\": 0\n      },\n      {\n        \"format\": 72,\n        \"name\": \"v1\",\n        \"score\": 0\n      },\n      {\n        \"format\": 73,\n        \"name\": \"v2\",\n        \"score\": 0\n      },\n      {\n        \"format\": 74,\n        \"name\": \"v3\",\n        \"score\": 0\n      },\n      {\n        \"format\": 75,\n        \"name\": \"v4\",\n        \"score\": 0\n      },\n      {\n        \"format\": 76,\n        \"name\": \"AV1\",\n        \"score\": 0\n      },\n      {\n        \"format\": 77,\n        \"name\": \"VOSTFR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 78,\n        \"name\": \"CR\",\n        \"score\": 0\n      },\n      {\n        \"format\": 79,\n        \"name\": \"FUNi\",\n        \"score\": 0\n      },\n      {\n        \"format\": 80,\n        \"name\": \"VRV\",\n        \"score\": 0\n      },\n      {\n        \"format\": 81,\n        \"name\": \"ADN\",\n        \"score\": 0\n      },\n      {\n        \"format\": 82,\n        \"name\": \"B-Global\",\n        \"score\": 0\n      },\n      {\n        \"format\": 83,\n        \"name\": \"Bilibili\",\n        \"score\": 0\n      },\n      {\n        \"format\": 84,\n        \"name\": \"HIDIVE\",\n        \"score\": 0\n      },\n      {\n        \"format\": 85,\n        \"name\": \"ABEMA\",\n        \"score\": 0\n      },\n      {\n        \"format\": 86,\n        \"name\": \"Remux Tier 01\",\n        \"score\": 0\n      },\n      {\n        \"format\": 87,\n        \"name\": \"Remux Tier 02\",\n        \"score\": 0\n      },\n      {\n        \"format\": 88,\n        \"name\": \"DV (WEBDL)\",\n        \"score\": 0\n      },\n      {\n        \"format\": 90,\n        \"name\": \"Uncensored\",\n        \"score\": 0\n      },\n      {\n        \"format\": 91,\n        \"name\": \"10bit\",\n        \"score\": 0\n      },\n      {\n        \"format\": 92,\n        \"name\": \"Anime Dual Audio\",\n        \"score\": 0\n      },\n      {\n        \"format\": 94,\n        \"name\": \"Language: Not German or English\",\n        \"score\": 0\n      },\n      {\n        \"format\": 95,\n        \"name\": \"Language: Prefer German\",\n        \"score\": 0\n      }\n    ],\n    \"id\": 20\n  }\n]\n"
  },
  {
    "path": "tests/samples/quality_with_grouping.json",
    "content": "{\n  \"name\": \"WEB 1080p\",\n  \"items\": [\n    {\n      \"quality\": {\n        \"id\": 15,\n        \"name\": \"WEBRip-1080p\",\n        \"source\": \"webRip\",\n        \"resolution\": 1080\n      },\n      \"items\": [],\n      \"allowed\": true\n    },\n    {\n      \"quality\": {\n        \"id\": 3,\n        \"name\": \"WEBDL-1080p\",\n        \"source\": \"web\",\n        \"resolution\": 1080\n      },\n      \"items\": [],\n      \"allowed\": true\n    }\n  ],\n  \"allowed\": true,\n  \"id\": 1002\n}\n"
  },
  {
    "path": "tests/samples/quality_without_grouping.json",
    "content": "{\n  \"quality\": {\n    \"id\": 5,\n    \"name\": \"WEBDL-720p\",\n    \"source\": \"web\",\n    \"resolution\": 720\n  },\n  \"items\": [],\n  \"allowed\": true\n}\n"
  },
  {
    "path": "tests/samples/single_custom_format.json",
    "content": "{\n  \"id\": 1,\n  \"name\": \"BR-DISK\",\n  \"includeCustomFormatWhenRenaming\": false,\n  \"specifications\": [\n    {\n      \"name\": \"BR-DISK\",\n      \"implementation\": \"ReleaseTitleSpecification\",\n      \"implementationName\": \"Release Title\",\n      \"infoLink\": \"https://wiki.servarr.com/sonarr/settings#custom-formats-2\",\n      \"negate\": false,\n      \"required\": true,\n      \"fields\": [\n        {\n          \"order\": 0,\n          \"name\": \"value\",\n          \"label\": \"Regular Expression\",\n          \"helpText\": \"Custom Format RegEx is Case Insensitive\",\n          \"value\": \"^(?!.*\\\\b((?<!HD[._ -]|HD)DVD|BDRip|720p|MKV|XviD|WMV|d3g|(BD)?REMUX|^(?=.*1080p)(?=.*HEVC)|[xh][-_. ]?26[45]|German.*[DM]L|((?<=\\\\d{4}).*German.*([DM]L)?)(?=.*\\\\b(AVC|HEVC|VC[-_. ]?1|MVC|MPEG[-_. ]?2)\\\\b))\\\\b)(((?=.*\\\\b(Blu[-_. ]?ray|BD|HD[-_. ]?DVD)\\\\b)(?=.*\\\\b(AVC|HEVC|VC[-_. ]?1|MVC|MPEG[-_. ]?2|BDMV|ISO)\\\\b))|^((?=.*\\\\b(((?=.*\\\\b((.*_)?COMPLETE.*|Dis[ck])\\\\b)(?=.*(Blu[-_. ]?ray|HD[-_. ]?DVD)))|3D[-_. ]?BD|BR[-_. ]?DISK|Full[-_. ]?Blu[-_. ]?ray|^((?=.*((BD|UHD)[-_. ]?(25|50|66|100|ISO)))))))).*\",\n          \"type\": \"textbox\",\n          \"advanced\": false,\n          \"privacy\": \"normal\",\n          \"isFloat\": false\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "tests/samples/single_quality_profile.json",
    "content": "{\n  \"name\": \"TestProfile\",\n  \"upgradeAllowed\": true,\n  \"cutoff\": 1003,\n  \"items\": [\n    {\n      \"quality\": {\n        \"id\": 0,\n        \"name\": \"Unknown\",\n        \"source\": \"unknown\",\n        \"resolution\": 0\n      },\n      \"items\": [],\n      \"allowed\": false\n    },\n    {\n      \"quality\": {\n        \"id\": 1,\n        \"name\": \"SDTV\",\n        \"source\": \"television\",\n        \"resolution\": 480\n      },\n      \"items\": [],\n      \"allowed\": false\n    },\n    {\n      \"name\": \"WEB 480p\",\n      \"items\": [\n        {\n          \"quality\": {\n            \"id\": 12,\n            \"name\": \"WEBRip-480p\",\n            \"source\": \"webRip\",\n            \"resolution\": 480\n          },\n          \"items\": [],\n          \"allowed\": false\n        },\n        {\n          \"quality\": {\n            \"id\": 8,\n            \"name\": \"WEBDL-480p\",\n            \"source\": \"web\",\n            \"resolution\": 480\n          },\n          \"items\": [],\n          \"allowed\": false\n        }\n      ],\n      \"allowed\": false,\n      \"id\": 1000\n    },\n    {\n      \"quality\": {\n        \"id\": 2,\n        \"name\": \"DVD\",\n        \"source\": \"dvd\",\n        \"resolution\": 480\n      },\n      \"items\": [],\n      \"allowed\": false\n    },\n    {\n      \"quality\": {\n        \"id\": 13,\n        \"name\": \"Bluray-480p\",\n        \"source\": \"bluray\",\n        \"resolution\": 480\n      },\n      \"items\": [],\n      \"allowed\": false\n    },\n    {\n      \"quality\": {\n        \"id\": 10,\n        \"name\": \"Raw-HD\",\n        \"source\": \"televisionRaw\",\n        \"resolution\": 1080\n      },\n      \"items\": [],\n      \"allowed\": false\n    },\n    {\n      \"quality\": {\n        \"id\": 6,\n        \"name\": \"Bluray-720p\",\n        \"source\": \"bluray\",\n        \"resolution\": 720\n      },\n      \"items\": [],\n      \"allowed\": false\n    },\n    {\n      \"quality\": {\n        \"id\": 7,\n        \"name\": \"Bluray-1080p\",\n        \"source\": \"bluray\",\n        \"resolution\": 1080\n      },\n      \"items\": [],\n      \"allowed\": false\n    },\n    {\n      \"quality\": {\n        \"id\": 20,\n        \"name\": \"Bluray-1080p Remux\",\n        \"source\": \"blurayRaw\",\n        \"resolution\": 1080\n      },\n      \"items\": [],\n      \"allowed\": false\n    },\n    {\n      \"quality\": {\n        \"id\": 19,\n        \"name\": \"Bluray-2160p\",\n        \"source\": \"bluray\",\n        \"resolution\": 2160\n      },\n      \"items\": [],\n      \"allowed\": false\n    },\n    {\n      \"quality\": {\n        \"id\": 21,\n        \"name\": \"Bluray-2160p Remux\",\n        \"source\": \"blurayRaw\",\n        \"resolution\": 2160\n      },\n      \"items\": [],\n      \"allowed\": false\n    },\n    {\n      \"name\": \"WEB 720p\",\n      \"items\": [\n        {\n          \"quality\": {\n            \"id\": 4,\n            \"name\": \"HDTV-720p\",\n            \"source\": \"television\",\n            \"resolution\": 720\n          },\n          \"items\": [],\n          \"allowed\": false\n        },\n        {\n          \"quality\": {\n            \"id\": 14,\n            \"name\": \"WEBRip-720p\",\n            \"source\": \"webRip\",\n            \"resolution\": 720\n          },\n          \"items\": [],\n          \"allowed\": false\n        },\n        {\n          \"quality\": {\n            \"id\": 5,\n            \"name\": \"WEBDL-720p\",\n            \"source\": \"web\",\n            \"resolution\": 720\n          },\n          \"items\": [],\n          \"allowed\": false\n        }\n      ],\n      \"allowed\": false,\n      \"id\": 1001\n    },\n    {\n      \"name\": \"WEB 1080p\",\n      \"items\": [\n        {\n          \"quality\": {\n            \"id\": 9,\n            \"name\": \"HDTV-1080p\",\n            \"source\": \"television\",\n            \"resolution\": 1080\n          },\n          \"items\": [],\n          \"allowed\": false\n        },\n        {\n          \"quality\": {\n            \"id\": 15,\n            \"name\": \"WEBRip-1080p\",\n            \"source\": \"webRip\",\n            \"resolution\": 1080\n          },\n          \"items\": [],\n          \"allowed\": false\n        },\n        {\n          \"quality\": {\n            \"id\": 3,\n            \"name\": \"WEBDL-1080p\",\n            \"source\": \"web\",\n            \"resolution\": 1080\n          },\n          \"items\": [],\n          \"allowed\": false\n        }\n      ],\n      \"allowed\": false,\n      \"id\": 1002\n    },\n    {\n      \"quality\": {\n        \"id\": 16,\n        \"name\": \"HDTV-2160p\",\n        \"source\": \"television\",\n        \"resolution\": 2160\n      },\n      \"items\": [],\n      \"allowed\": false\n    },\n    {\n      \"name\": \"WEB 2160p\",\n      \"items\": [\n        {\n          \"quality\": {\n            \"id\": 17,\n            \"name\": \"WEBRip-2160p\",\n            \"source\": \"webRip\",\n            \"resolution\": 2160\n          },\n          \"items\": [],\n          \"allowed\": true\n        },\n        {\n          \"quality\": {\n            \"id\": 18,\n            \"name\": \"WEBDL-2160p\",\n            \"source\": \"web\",\n            \"resolution\": 2160\n          },\n          \"items\": [],\n          \"allowed\": true\n        }\n      ],\n      \"allowed\": true,\n      \"id\": 1003\n    }\n  ],\n  \"minFormatScore\": 0,\n  \"cutoffFormatScore\": 1000,\n  \"formatItems\": [],\n  \"id\": 1000\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    // breaks esbuild import\n    // \"baseUrl\": \".\",\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"isolatedModules\": true,\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"noUncheckedIndexedAccess\": true,\n    // \"paths\": {\n    //   \"~/*\": [\"./src/*\"]\n    // },\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"strictNullChecks\": true,\n    \"target\": \"ESNext\",\n    \"noEmit\": true\n  },\n  \"exclude\": [\"./dist\", \"./docs\"],\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"]\n}\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n  test: {\n    include: [\"src/**/*.test.ts\"],\n  },\n});\n"
  }
]