diff --git a/.github/agents/tests.agent.md b/.github/agents/tests.agent.md index 6f2a523..f805eaa 100644 --- a/.github/agents/tests.agent.md +++ b/.github/agents/tests.agent.md @@ -1,5 +1,5 @@ --- -description: "Use when developing tests for TorBoxSDK: create or review unit tests, integration tests, and performance tests for the TorBox C# SDK based on changes in the core project." +description: "Use when developing tests for TorBoxSDK: create or review unit tests, integration tests, schema validation tests, and performance tests for the TorBox C# SDK based on changes in the core project." name: "Tests" tools: [vscode, execute, read, agent, edit, search, web, browser, 'com.postman/postman-mcp-server/*', 'github/*', 'microsoftdocs/mcp/*', vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/pullRequestStatusChecks, github.vscode-pull-request-github/openPullRequest, todo] model: Claude Opus 4.6 (copilot) @@ -17,6 +17,7 @@ Your job is to build and maintain the test side of the SDK based on what exists Focus only on test development for TorBoxSDK: - unit tests - integration tests +- schema validation tests (static OpenAPI comparison and live field detection) - performance tests Your responsibility is to validate the public behavior of the SDK while preserving the project's structure and conventions. @@ -43,6 +44,7 @@ Use this agent for: - adding unit tests for `TorBoxClient`, API clients, and resource clients - validating request construction, route selection, auth headers, and response mapping - testing JSON serialization and deserialization behavior +- adding or updating schema validation tests against the TorBox OpenAPI specification (static field/type coverage and live field detection) - adding integration tests against the live TorBox APIs when appropriate - adding or updating performance tests for hot serialization or HTTP-client code paths - reviewing whether current coverage matches changes in the core SDK @@ -62,14 +64,15 @@ If a test requires a minimal production-code seam to become testable, keep that ## Workflow -1. Identify whether the task belongs to unit, integration, or performance testing. +1. Identify whether the task belongs to unit, integration, schema validation, or performance testing. 2. Inspect the production behavior under test before writing assertions. 3. Prefer unit tests first, especially for request shape, response mapping, and error handling. -4. Add integration tests only when live API behavior provides real value. -5. Add performance tests only when they answer a concrete throughput or allocation question. -6. If the task requires updating `README.md` or Markdown files under `docs/`, delegate that work to `Docs`. -7. Run relevant tests or validate the test project setup before finishing when possible. -8. Report any remaining gaps in coverage, flaky risks, or missing seams. +4. When models change, update schema validation mappings in `SchemaModelMapping` and add live tests for new endpoints. +5. Add integration tests only when live API behavior provides real value. +6. Add performance tests only when they answer a concrete throughput or allocation question. +7. If the task requires updating `README.md` or Markdown files under `docs/`, delegate that work to `Docs`. +8. Run relevant tests or validate the test project setup before finishing when possible. +9. Report any remaining gaps in coverage, flaky risks, or missing seams. ## Done When @@ -87,4 +90,4 @@ When working on a task, prefer to return: - the production behavior covered - the strategy used, such as unit vs integration vs performance - any documentation handoff required for `Docs` -- any remaining coverage gaps, assumptions, or follow-up work \ No newline at end of file +- any remaining coverage gaps, assumptions, or follow-up work diff --git a/.github/instructions/csharp-conventions.instructions.md b/.github/instructions/csharp-conventions.instructions.md index 99b5fde..0a91019 100644 --- a/.github/instructions/csharp-conventions.instructions.md +++ b/.github/instructions/csharp-conventions.instructions.md @@ -515,6 +515,42 @@ public async Task GetMeAsync_WithRealApiKey_ReturnsUser() - No I/O in baseline benchmarks unless measuring HTTP overhead - Do not delete existing benchmarks without justification +## Schema Validation Tests (`TorBoxSDK.SchemaValidationTests`) + +Schema tests verify bidirectional consistency between the TorBox OpenAPI specification and SDK model types. The OpenAPI spec is fetched at test time from `https://api.torbox.app/openapi.json` — no local copy is versioned. + +### Static Schema Tests (`OpenApi/`) + +Static schema tests compare the downloaded OpenAPI specification against SDK model types without calling the API: + +- `OpenApiFieldCoverageTests` — verifies all OpenAPI fields are mapped in the SDK and vice-versa +- `OpenApiTypeMappingTests` — verifies C# property types are compatible with OpenAPI type declarations + +Key rules: +- Use `OpenApiSchemaReader.ReadFromApi()` for all spec access — never read from a local file +- Register new schema-to-type mappings in `SchemaModelMapping.SchemaToType` +- Record intentional field discrepancies in `SchemaModelMapping.KnownOpenApiFieldsNotInSdk` or `KnownSdkFieldsNotInOpenApi` +- Record intentional type mismatches in `SchemaModelMapping.KnownTypeMismatches` +- Filter static tests in CI with `--filter "Category!=Live"` + +### Live Schema Tests (`Live/`) + +Live schema tests call real TorBox API endpoints and check for unmapped fields: + +- `[Trait("Category", "Live")]` on every live test class +- `[Collection("SchemaLive")]` to share `SchemaLiveTestFixture` +- Always skip gracefully when `TORBOX_API_KEY` is absent via `Skip.If(!Fixture.HasApiKey)` +- Use `SchemaAssert.FindUnmappedFieldsAsync()` for field detection +- Use `EnsureSuccessStatusCode()` and `using` on every `HttpResponseMessage` + +### Shared Infrastructure + +Infrastructure classes in `Infrastructure/` delegate to `TorBoxSDK.TestUtilities`: +- `ModelReflector` — `[JsonPropertyName]` reflection +- `UnmappedFieldDetector` — recursive unmapped JSON field detection +- `OpenApiSchemaReader` — OpenAPI spec download and parsing (cached per process) +- `SchemaModelMapping` — schema-to-type map with known exclusions and type mismatches + --- # Part 5 — Samples (`samples/**/*.cs`) diff --git a/.github/skills/code-review/references/instruction-map.md b/.github/skills/code-review/references/instruction-map.md index 784f8eb..b105d37 100644 --- a/.github/skills/code-review/references/instruction-map.md +++ b/.github/skills/code-review/references/instruction-map.md @@ -45,5 +45,6 @@ Is the file in tests/? | DI extensions | `src/TorBoxSDK/Extensions/` | 1 + 2 | | Unit tests | `tests/TorboxSDK.UnitTests/` | 1 + 4 | | Integration tests | `tests/TorBoxSDK.IntegrationTests/` | 1 + 4 | +| Schema validation tests | `tests/TorBoxSDK.SchemaValidationTests/` | 1 + 4 | | Performance tests | `tests/TorBoxSDK.PerformanceTests/` | 1 + 4 | | Samples | `samples/` | 1 + 5 | diff --git a/.github/skills/dev/SKILL.md b/.github/skills/dev/SKILL.md index dee50f7..d650800 100644 --- a/.github/skills/dev/SKILL.md +++ b/.github/skills/dev/SKILL.md @@ -35,7 +35,7 @@ Endpoint implementation is now owned directly by `/dev` as **J2 — Endpoint** t |-----|---------|---------------| | **J1 — Architecture** | Designing or refactoring client structure, DI, namespacing, cross-cutting concerns | `architecture` skill | | **J2 — Endpoint** | Adding or extending an API endpoint (models + client method + tests) | Internal `/dev` endpoint workflow | -| **J3 — Tests** | Writing unit, integration, or performance tests for existing code | `tests` skill | +| **J3 — Tests** | Writing unit, integration, schema validation, or performance tests for existing code | `tests` skill | | **J4 — Review** | Reviewing or auditing C# code before merge | `code-review` skill | | **J5 — Docs & Packaging** | README, samples, XML docs, NuGet metadata, release readiness | `docs` skill | | **J6 — Foundation** | Scaffolding project infrastructure (csproj, build props, editorconfig, DI setup) | See [foundation jobs reference](./references/dev-jobs.md#j6--foundation-jobs) | diff --git a/.github/skills/dev/references/dev-jobs.md b/.github/skills/dev/references/dev-jobs.md index f662416..7af6972 100644 --- a/.github/skills/dev/references/dev-jobs.md +++ b/.github/skills/dev/references/dev-jobs.md @@ -108,6 +108,7 @@ For each endpoint: |------|-------|-------------| | Unit tests | After each J2 implementation | `tests/TorboxSDK.UnitTests/` | | Serialization tests | After each new model (J2 Phase 2) | `tests/TorboxSDK.UnitTests/Models/` | +| Schema validation tests | After each new or modified model | `tests/TorBoxSDK.SchemaValidationTests/` | | Integration tests | After stabilization of a complete resource client | `tests/TorBoxSDK.IntegrationTests/` | | Performance tests | On critical paths (serialization, HTTP) | `tests/TorBoxSDK.PerformanceTests/` | diff --git a/.github/skills/dev/references/endpoint-implementation-checklist.md b/.github/skills/dev/references/endpoint-implementation-checklist.md index b9a36e0..c7dc662 100644 --- a/.github/skills/dev/references/endpoint-implementation-checklist.md +++ b/.github/skills/dev/references/endpoint-implementation-checklist.md @@ -31,4 +31,6 @@ Use this checklist when executing **J2 — Endpoint** inside `/dev`. - Add unit tests for success. - Add unit tests for failure. - Assert the route, method, and payload. +- Register new models in `SchemaModelMapping.SchemaToType` for schema validation. +- Run static schema tests to verify field and type coverage. - Update docs if the public API changed. diff --git a/.github/skills/dev/references/skill-map.md b/.github/skills/dev/references/skill-map.md index 627f175..24b2404 100644 --- a/.github/skills/dev/references/skill-map.md +++ b/.github/skills/dev/references/skill-map.md @@ -12,7 +12,7 @@ Quick reference to determine which skill to load based on the nature of the work dev (orchestrator skill) ├── architecture → client structure, DI, namespaces ├── J2 endpoint workflow → models + client method + HTTP wiring -├── tests → unit tests, integration, perf +├── tests → unit tests, schema validation, integration, perf ├── code-review → audit and validation before merge └── docs → README, docs pages, samples, XML doc, diagrams, NuGet ``` @@ -33,6 +33,7 @@ The `dev` skill does not replace these skills. It chooses which one to launch, i | "Extend a resource client interface" | `dev` (J2) | `.github/skills/dev/SKILL.md` | | "Write unit tests" | `tests` | `.github/skills/tests/SKILL.md` | | "Write integration tests" | `tests` | `.github/skills/tests/SKILL.md` | +| "Update schema validation tests" | `tests` | `.github/skills/tests/SKILL.md` | | "Add benchmarks" | `tests` | `.github/skills/tests/SKILL.md` | | "Review this file" | `code-review` | `.github/skills/code-review/SKILL.md` | | "Audit src/ before merge" | `code-review` | `.github/skills/code-review/SKILL.md` | diff --git a/.github/skills/tests/SKILL.md b/.github/skills/tests/SKILL.md index 56e99dd..87c2022 100644 --- a/.github/skills/tests/SKILL.md +++ b/.github/skills/tests/SKILL.md @@ -1,6 +1,6 @@ --- name: tests -description: 'Use when writing or reviewing tests for TorBoxSDK: unit tests for HttpClient-based services, integration tests against TorBox, serialization tests, and performance validation.' +description: 'Use when writing or reviewing tests for TorBoxSDK: unit tests for HttpClient-based services, integration tests against TorBox, schema validation tests against the OpenAPI spec, serialization tests, and performance validation.' argument-hint: 'Describe the service, model, or behavior to test, such as TorrentsService request mapping or TorBoxResponse deserialization.' user-invocable: true disable-model-invocation: false @@ -18,6 +18,7 @@ Use this skill when you need to: - write unit tests for a service that uses HttpClient - validate JSON serialization or deserialization - add integration tests that hit the real TorBox API +- add or update schema validation tests against the TorBox OpenAPI specification - review whether a new endpoint implementation is sufficiently tested - add performance or throughput-oriented validation @@ -25,6 +26,7 @@ Use this skill when you need to: Use the lightest test type that gives confidence: - unit tests for request construction, serialization, and error mapping +- schema validation tests for bidirectional model consistency with the OpenAPI spec - integration tests for real API behavior and auth-sensitive flows - performance tests only for code paths where allocation or throughput matters @@ -56,7 +58,16 @@ Use integration tests for: 5. Keep integration tests isolated. Mark them clearly so they can be excluded in default local and CI runs when credentials are unavailable. -6. Add performance tests selectively. +6. Update schema validation tests when models change. +When adding or modifying SDK models: +- add or update mappings in `SchemaModelMapping.SchemaToType` +- register any intentional field or type discrepancies in the appropriate known exclusion sets +- ensure static schema tests pass by running with `--filter "Category!=Live"` +- add live schema tests when a new endpoint is mapped, using `SchemaAssert.FindUnmappedFieldsAsync()` + +The OpenAPI specification is fetched from `https://api.torbox.app/openapi.json` at test time — no local file is versioned. + +7. Add performance tests selectively. Use them for: - heavy serialization paths - bulk list deserialization @@ -68,6 +79,7 @@ A test set is sufficient when: - the public behavior is validated, not just internals - success and failure paths both exist - serialization assumptions are pinned down +- schema validation mappings are current for any new or changed models - tests remain deterministic unless explicitly integration-based - the tests follow the repo xUnit conventions @@ -75,3 +87,4 @@ A test set is sufficient when: - [HTTP client unit testing patterns](./references/http-client-unit-testing-patterns.md) - [Integration and performance testing guidance](./references/integration-and-performance-testing-guidance.md) +- [Schema validation testing guidance](./references/schema-validation-testing-guidance.md) diff --git a/.github/skills/tests/references/integration-and-performance-testing-guidance.md b/.github/skills/tests/references/integration-and-performance-testing-guidance.md index e93a094..712b88b 100644 --- a/.github/skills/tests/references/integration-and-performance-testing-guidance.md +++ b/.github/skills/tests/references/integration-and-performance-testing-guidance.md @@ -15,6 +15,16 @@ Use integration tests only where live behavior matters: - Avoid brittle assertions on rapidly changing remote state. - Prefer test accounts or isolated resources when available. +## Schema Validation Tests + +Schema validation tests compare SDK models against the TorBox OpenAPI specification. See [schema-validation-testing-guidance.md](./schema-validation-testing-guidance.md) for full details. + +Key points: +- The OpenAPI spec is fetched from `https://api.torbox.app/openapi.json` — no local file. +- Static tests (field coverage, type mapping) run without API key. +- Live tests (unmapped field detection) require `TORBOX_API_KEY` and skip gracefully when absent. +- When adding new models, register them in `SchemaModelMapping.SchemaToType`. + ## Performance Use performance tests selectively for: diff --git a/.github/skills/tests/references/schema-validation-testing-guidance.md b/.github/skills/tests/references/schema-validation-testing-guidance.md new file mode 100644 index 0000000..394d26b --- /dev/null +++ b/.github/skills/tests/references/schema-validation-testing-guidance.md @@ -0,0 +1,56 @@ +# Schema Validation Testing + +## Overview + +Schema validation tests verify bidirectional consistency between the TorBox OpenAPI specification and SDK model types. The OpenAPI spec is fetched at test time from `https://api.torbox.app/openapi.json` — no local copy is versioned in the repository. + +## Test Categories + +### Static Schema Tests (`OpenApi/`) + +Compare the downloaded OpenAPI specification against SDK types without calling the API: + +- **`OpenApiFieldCoverageTests`** — every OpenAPI field must have a `[JsonPropertyName]` in the SDK, and vice-versa. +- **`OpenApiTypeMappingTests`** — C# property types must be compatible with OpenAPI type declarations. + +Static tests run in CI with `--filter "Category!=Live"` and require network access to download the spec. + +### Live Schema Tests (`Live/`) + +Call real TorBox API endpoints and detect unmapped fields in the JSON response: + +- Require `TORBOX_API_KEY` environment variable. +- Skip gracefully when the key is absent. +- Use `SchemaAssert.FindUnmappedFieldsAsync()` for field detection. +- Run in CI via the `integration-tests.yml` workflow with the `testing` environment. + +## Adding a New Model + +When adding or updating an SDK model: + +1. **Register the mapping** in `SchemaModelMapping.SchemaToType` — the key is the OpenAPI schema name, the value is the SDK model type. +2. **Run static tests** with `dotnet test tests/TorBoxSDK.SchemaValidationTests/ --filter "Category!=Live"` to verify field and type coverage. +3. **Add known exclusions** if the SDK intentionally diverges: + - `KnownOpenApiFieldsNotInSdk` — OpenAPI fields the SDK does not map (e.g., deprecated or internal-only fields). + - `KnownSdkFieldsNotInOpenApi` — SDK extensions not in the spec (e.g., computed properties). + - `KnownTypeMismatches` — fields where the OpenAPI type differs from the SDK serialization (e.g., integer enums serialized as strings). +4. **Add a live test** when a new endpoint is mapped, following the pattern in existing `Live/` test files. + +## Infrastructure + +| Class | Location | Responsibility | +|-------|----------|----------------| +| `OpenApiSchemaReader` | `Infrastructure/` | Downloads and parses the OpenAPI spec (cached per process via `Lazy`) | +| `SchemaModelMapping` | `Infrastructure/` | Schema-to-type map, known exclusions, type mapping | +| `ModelReflector` | `Infrastructure/` | `[JsonPropertyName]` reflection (delegates to `TestUtilities`) | +| `UnmappedFieldDetector` | `Infrastructure/` | Recursive unmapped field detection (delegates to `TestUtilities`) | +| `SchemaLiveTestFixture` | `Infrastructure/` | Shared `HttpClient` with API key for live tests | +| `SchemaTestCollection` | `Infrastructure/` | xUnit collection definition for live test fixture sharing | + +## Key Rules + +- Never version `openapi.json` locally — always fetch from `https://api.torbox.app/openapi.json`. +- Use `OpenApiSchemaReader.ReadFromApi()` for all spec access. +- Static tests must work without `TORBOX_API_KEY`. +- Live tests must skip gracefully without `TORBOX_API_KEY`. +- All `HttpResponseMessage` in live tests must use `using` and `EnsureSuccessStatusCode()`. diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 3d127c0..cc918ca 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -8,12 +8,12 @@ on: run_live_schema: description: 'Run live schema validation tests' required: false - default: 'true' + default: true type: boolean run_integration: description: 'Run integration tests' required: false - default: 'true' + default: true type: boolean permissions: diff --git a/.gitignore b/.gitignore index 9491a2f..eaa9902 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,4 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd diff --git a/README.md b/README.md index 4869d30..a25bc88 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,14 @@ dotnet test tests/TorBoxSDK.IntegrationTests/ Integration tests are designed to skip gracefully when `TORBOX_API_KEY` is not set. +## Versioning + +This project follows [Semantic Versioning (SemVer)](https://semver.org). Each release version uses the `MAJOR.MINOR.PATCH` format: + +- **MAJOR** — incompatible API changes +- **MINOR** — new functionality added in a backward-compatible manner +- **PATCH** — backward-compatible bug fixes + ## License This repository is available under the [MIT License](LICENSE). diff --git a/TorBoxSDK.slnx b/TorBoxSDK.slnx index 6a8be55..5b14f2a 100644 --- a/TorBoxSDK.slnx +++ b/TorBoxSDK.slnx @@ -1,4 +1,8 @@ + + + + diff --git a/docs/getting-started.md b/docs/getting-started.md index d7c669c..9f06ed9 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -35,8 +35,7 @@ using TorBoxSDK.Models.Search; using TorBoxSDK.Models.Torrents; TorBoxResponse> torrents = await client.Main.Torrents.GetMyTorrentListAsync(); -TorBoxResponse results = - await client.Search.SearchTorrentsAsync("ubuntu"); +TorBoxResponse results = await client.Search.SearchTorrentsAsync("ubuntu"); ``` ## What to expect from responses diff --git a/open_api.json b/open_api.json deleted file mode 100644 index b358fae..0000000 --- a/open_api.json +++ /dev/null @@ -1,8013 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "TorBox API", - "description": "TorBox API Documentation", - "contact": { - "name": "TorBox", - "url": "https://torbox.app/", - "email": "contact@torbox.app" - }, - "version": "1.0.0" - }, - "servers": [ - { - "url": "https://api.torbox.app" - } - ], - "paths": { - "/v1/api/user/refreshtoken": { - "post": { - "tags": [ - "user" - ], - "summary": "Refresh Token", - "description": "Gives the user a new API token. Requires the user's session token from the website due to security concerns.", - "operationId": "refresh_token_v1_api_user_refreshtoken_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RefreshTokenSchema" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/user/getconfirmation": { - "get": { - "tags": [ - "user" - ], - "summary": "Get Confirmation Code", - "description": "Sends a confirmation code to the user's email to be used for account verification when performing certain actions.", - "operationId": "get_confirmation_code_v1_api_user_getconfirmation_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/user/me": { - "get": { - "tags": [ - "user" - ], - "summary": "Get User", - "description": "Returns the user's information, as well as settings if requested. Requires API key.", - "operationId": "get_user_v1_api_user_me_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "settings", - "in": "query", - "required": false, - "schema": { - "type": "boolean", - "default": false, - "title": "Settings" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/user/addreferral": { - "post": { - "tags": [ - "user" - ], - "summary": "Add Referral", - "description": "Adds a referral to the user's account. Does not replace a referral if one already exists.", - "operationId": "add_referral_v1_api_user_addreferral_post", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "referral", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "Referral" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/user/auth/device/start": { - "get": { - "tags": [ - "user" - ], - "summary": "Start Device Code Authorization", - "description": "Starts the device code authorization process.", - "operationId": "start_device_code_authorization_v1_api_user_auth_device_start_get", - "parameters": [ - { - "name": "app", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": "Third Party App", - "title": "App" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/user/auth/device/token": { - "post": { - "tags": [ - "user" - ], - "summary": "Get Token From Device Code", - "description": "Returns the user's token once the device code is authorized.", - "operationId": "get_token_from_device_code_v1_api_user_auth_device_token_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeviceTokenSchema" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/user/deleteme": { - "delete": { - "tags": [ - "user" - ], - "summary": "Delete User Account", - "operationId": "delete_user_account_v1_api_user_deleteme_delete", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeleteAccountSchema" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/user/referraldata": { - "get": { - "tags": [ - "user" - ], - "summary": "Get Referral Data", - "description": "Returns the user's referral data.", - "operationId": "get_referral_data_v1_api_user_referraldata_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/user/subscriptions": { - "get": { - "tags": [ - "user" - ], - "summary": "Get Subscriptions", - "description": "Returns the user's referral data.", - "operationId": "get_subscriptions_v1_api_user_subscriptions_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/user/transactions": { - "get": { - "tags": [ - "user" - ], - "summary": "Get Transactions", - "description": "Returns the user's transactions.", - "operationId": "get_transactions_v1_api_user_transactions_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/user/transaction/pdf": { - "get": { - "tags": [ - "user" - ], - "summary": "Get Transaction Pdf", - "description": "Returns the user's transaction as a PDF.", - "operationId": "get_transaction_pdf_v1_api_user_transaction_pdf_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "transaction_id", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "Transaction Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/user/settings/addsearchengines": { - "put": { - "tags": [ - "user" - ], - "summary": "Add Search Engine", - "description": "Adds a search engine to your account.", - "operationId": "add_search_engine_v1_api_user_settings_addsearchengines_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SearchEngineModel" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/user/settings/searchengines": { - "get": { - "tags": [ - "user" - ], - "summary": "Get Search Engines", - "description": "Returns the user's search engines.", - "operationId": "get_search_engines_v1_api_user_settings_searchengines_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "id", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/user/settings/modifysearchengines": { - "post": { - "tags": [ - "user" - ], - "summary": "Edit Search Engine", - "description": "Edits a search engine on your account.", - "operationId": "edit_search_engine_v1_api_user_settings_modifysearchengines_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SearchEngineEditModel" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/user/settings/controlsearchengines": { - "post": { - "tags": [ - "user" - ], - "summary": "Control Search Engine", - "description": "Controls a search engine on your account.", - "operationId": "control_search_engine_v1_api_user_settings_controlsearchengines_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ControlSearchEngine" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/user/settings/editsettings": { - "put": { - "tags": [ - "user" - ], - "summary": "Edit General Settings", - "description": "Edits the user's general settings.", - "operationId": "edit_general_settings_v1_api_user_settings_editsettings_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BaseSettingsModel" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/torrents/createtorrent": { - "post": { - "tags": [ - "torrents" - ], - "summary": "Create Torrent", - "description": "Creates a torrent from a file or magnet link. Requires API key.", - "operationId": "create_torrent_v1_api_torrents_createtorrent_post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_torrent_v1_api_torrents_createtorrent_post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/torrents/asynccreatetorrent": { - "post": { - "tags": [ - "torrents" - ], - "summary": "Async Create Torrent", - "description": "Creates a torrent from a file or magnet link. Requires API key. This is an async version of the /createtorrent endpoint that returns immediately and processes the request in the background.", - "operationId": "async_create_torrent_v1_api_torrents_asynccreatetorrent_post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_async_create_torrent_v1_api_torrents_asynccreatetorrent_post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/torrents/controltorrent": { - "post": { - "tags": [ - "torrents" - ], - "summary": "Control Torrent", - "description": "Controls a torrent. Requires API key.", - "operationId": "control_torrent_v1_api_torrents_controltorrent_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ControlTorrent" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/torrents/getqueued": { - "get": { - "tags": [ - "torrents" - ], - "summary": "Get Queued Torrents", - "description": "Gets a list of queued torrents. Requires API key.", - "operationId": "get_queued_torrents_v1_api_torrents_getqueued_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/torrents/controlqueued": { - "post": { - "tags": [ - "torrents" - ], - "summary": "Control Queued", - "description": "Controls a queued torrent. Requires API key.", - "operationId": "control_queued_v1_api_torrents_controlqueued_post", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/torrents/requestdl": { - "get": { - "tags": [ - "torrents" - ], - "summary": "Request Download", - "operationId": "request_download_v1_api_torrents_requestdl_get", - "parameters": [ - { - "name": "token", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "Token" - } - }, - { - "name": "torrent_id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "title": "Torrent Id" - } - }, - { - "name": "file_id", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 0, - "title": "File Id" - } - }, - { - "name": "zip_link", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Zip Link" - } - }, - { - "name": "user_ip", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "User Ip" - } - }, - { - "name": "redirect", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Redirect" - } - }, - { - "name": "append_name", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Append Name" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/torrents/mylist": { - "get": { - "tags": [ - "torrents" - ], - "summary": "Get My Torrent List", - "operationId": "get_my_torrent_list_v1_api_torrents_mylist_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "id", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Id" - } - }, - { - "name": "bypass_cache", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Bypass Cache" - } - }, - { - "name": "offset", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 0, - "title": "Offset" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 1000, - "title": "Limit" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/torrents/checkcached": { - "get": { - "tags": [ - "torrents" - ], - "summary": "Check Cached Torrent", - "operationId": "check_cached_torrent_v1_api_torrents_checkcached_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "hash", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "null" - } - ], - "title": "Hash" - } - }, - { - "name": "format", - "in": "query", - "required": false, - "schema": { - "type": "string", - "default": "object", - "title": "Format" - } - }, - { - "name": "list_files", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "List Files" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/CheckCached" - }, - { - "type": "null" - } - ], - "title": "Hash List" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - }, - "post": { - "tags": [ - "torrents" - ], - "summary": "Check Cached Torrent", - "operationId": "check_cached_torrent_v1_api_torrents_checkcached_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "hash", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "null" - } - ], - "title": "Hash" - } - }, - { - "name": "format", - "in": "query", - "required": false, - "schema": { - "type": "string", - "default": "object", - "title": "Format" - } - }, - { - "name": "list_files", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "List Files" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/CheckCached" - }, - { - "type": "null" - } - ], - "title": "Hash List" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/torrents/exportdata": { - "get": { - "tags": [ - "torrents" - ], - "summary": "Export Torrent Data", - "operationId": "export_torrent_data_v1_api_torrents_exportdata_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "torrent_id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "title": "Torrent Id" - } - }, - { - "name": "type", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "Type" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/torrents/magnettofile": { - "post": { - "tags": [ - "torrents" - ], - "summary": "Magnet To File", - "operationId": "magnet_to_file_v1_api_torrents_magnettofile_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MagnetToTorrent" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/torrents/torrentinfo": { - "get": { - "tags": [ - "torrents" - ], - "summary": "Get Torrent Info", - "operationId": "get_torrent_info_v1_api_torrents_torrentinfo_get", - "parameters": [ - { - "name": "hash", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "Hash" - } - }, - { - "name": "timeout", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 30, - "title": "Timeout" - } - }, - { - "name": "use_cache_lookup", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Use Cache Lookup" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - }, - "post": { - "tags": [ - "torrents" - ], - "summary": "Get Torrent Info Post", - "operationId": "get_torrent_info_post_v1_api_torrents_torrentinfo_post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_get_torrent_info_post_v1_api_torrents_torrentinfo_post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/torrents/edittorrent": { - "put": { - "tags": [ - "torrents" - ], - "summary": "Edit Torrent", - "description": "Edits a torrent's data. This overwrites the pre-existing data for the torrent. Item must be cached. Requires API key.", - "operationId": "edit_torrent_v1_api_torrents_edittorrent_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EditTorrent" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ] - } - }, - "/": { - "get": { - "tags": [ - "general" - ], - "summary": "Status", - "description": "The root endpoint. Simply returns the status of the API.", - "operationId": "status__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - } - } - }, - "/v1/api/stats": { - "get": { - "tags": [ - "general" - ], - "summary": "Get Stats", - "description": "The stats endpoint. Gets all of TorBox stats.", - "operationId": "get_stats_v1_api_stats_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - } - } - }, - "/v1/api/stats/30days": { - "get": { - "tags": [ - "general" - ], - "summary": "Get 30 Day Stats", - "description": "The 30 day stats endpoint. Gets all of TorBox stats for the last 30 days.", - "operationId": "get_30_day_stats_v1_api_stats_30days_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - } - } - }, - "/v1/api/notifications/rss": { - "get": { - "tags": [ - "general" - ], - "summary": "Get Rss Notifications", - "description": "The RSS notifications endpoint. Returns an RSS feed of all notifications for a user. Requires API key.", - "operationId": "get_rss_notifications_v1_api_notifications_rss_get", - "parameters": [ - { - "name": "token", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "Token" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/notifications/mynotifications": { - "get": { - "tags": [ - "general" - ], - "summary": "Get Notifications", - "description": "The notifications endpoint. Returns all notifications for a user. Requires API key.", - "operationId": "get_notifications_v1_api_notifications_mynotifications_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/notifications/clear": { - "post": { - "tags": [ - "general" - ], - "summary": "Clear All Notifications", - "description": "The clear notifications endpoint. Clears all notifications for a user. Requires API key.", - "operationId": "clear_all_notifications_v1_api_notifications_clear_post", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/notifications/clear/{id}": { - "post": { - "tags": [ - "general" - ], - "summary": "Clear Notification", - "description": "The clear notification endpoint. Clears a single notification for a user. Requires API key.", - "operationId": "clear_notification_v1_api_notifications_clear__id__post", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/notifications/test": { - "post": { - "tags": [ - "general" - ], - "summary": "Test Notification", - "description": "The test notifications endpoint. Sends a test notification to the user. Requires API key. Rate limited to 1 per minute.", - "operationId": "test_notification_v1_api_notifications_test_post", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/intercom/hash": { - "get": { - "tags": [ - "general" - ], - "summary": "Get Intercom Hash", - "description": "The Intercom hash endpoint. Returns a hashed ID and email for Intercom.", - "operationId": "get_intercom_hash_v1_api_intercom_hash_get", - "parameters": [ - { - "name": "auth_id", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "Auth Id" - } - }, - { - "name": "email", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "Email" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/changelogs/rss": { - "get": { - "tags": [ - "general" - ], - "summary": "Get Rss Changelog", - "description": "The RSS changelog endpoint. Returns an RSS feed for the most recent 100 changelogs.", - "operationId": "get_rss_changelog_v1_api_changelogs_rss_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - } - } - }, - "/v1/api/changelogs/json": { - "get": { - "tags": [ - "general" - ], - "summary": "Get Json Changelog", - "description": "The JSON changelog endpoint. Returns a JSON object for the most recent 100 changelogs.", - "operationId": "get_json_changelog_v1_api_changelogs_json_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - } - } - }, - "/v1/api/speedtest": { - "get": { - "tags": [ - "general" - ], - "summary": "Get Speedtest Files", - "description": "Gets the routes based on the options the user has selected for the speed test.", - "operationId": "get_speedtest_files_v1_api_speedtest_get", - "parameters": [ - { - "name": "user_ip", - "in": "query", - "required": false, - "schema": { - "type": "string", - "title": "User Ip" - } - }, - { - "name": "region", - "in": "query", - "required": false, - "schema": { - "type": "string", - "default": "all", - "title": "Region" - } - }, - { - "name": "test_length", - "in": "query", - "required": false, - "schema": { - "type": "string", - "default": "short", - "title": "Test Length" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/webdl/createwebdownload": { - "post": { - "tags": [ - "web downloads" - ], - "summary": "Create Web Download", - "description": "Creates a web download from a link. Requires API key.", - "operationId": "create_web_download_v1_api_webdl_createwebdownload_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_create_web_download_v1_api_webdl_createwebdownload_post" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/webdl/asynccreatewebdownload": { - "post": { - "tags": [ - "web downloads" - ], - "summary": "Async Create Web Download", - "description": "Creates a web download from a link. Requires API key.", - "operationId": "async_create_web_download_v1_api_webdl_asynccreatewebdownload_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_async_create_web_download_v1_api_webdl_asynccreatewebdownload_post" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/webdl/controlwebdownload": { - "post": { - "tags": [ - "web downloads" - ], - "summary": "Control Web Download", - "description": "Controls a web download. Requires API key.", - "operationId": "control_web_download_v1_api_webdl_controlwebdownload_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ControlWebDownload" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/webdl/requestdl": { - "get": { - "tags": [ - "web downloads" - ], - "summary": "Request Web Download", - "operationId": "request_web_download_v1_api_webdl_requestdl_get", - "parameters": [ - { - "name": "token", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "Token" - } - }, - { - "name": "web_id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "title": "Web Id" - } - }, - { - "name": "file_id", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 0, - "title": "File Id" - } - }, - { - "name": "zip_link", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Zip Link" - } - }, - { - "name": "user_ip", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "User Ip" - } - }, - { - "name": "redirect", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Redirect" - } - }, - { - "name": "append_name", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Append Name" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/webdl/mylist": { - "get": { - "tags": [ - "web downloads" - ], - "summary": "Get My Webdownloads List", - "operationId": "get_my_webdownloads_list_v1_api_webdl_mylist_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "id", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Id" - } - }, - { - "name": "bypass_cache", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Bypass Cache" - } - }, - { - "name": "offset", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 0, - "title": "Offset" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 1000, - "title": "Limit" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/webdl/checkcached": { - "get": { - "tags": [ - "web downloads" - ], - "summary": "Check Cached Webdownload", - "operationId": "check_cached_webdownload_v1_api_webdl_checkcached_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "hash", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "null" - } - ], - "title": "Hash" - } - }, - { - "name": "format", - "in": "query", - "required": false, - "schema": { - "type": "string", - "default": "object", - "title": "Format" - } - }, - { - "name": "list_files", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "List Files" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/CheckCached" - }, - { - "type": "null" - } - ], - "title": "Hash List" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - }, - "post": { - "tags": [ - "web downloads" - ], - "summary": "Check Cached Webdownload", - "operationId": "check_cached_webdownload_v1_api_webdl_checkcached_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "hash", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "null" - } - ], - "title": "Hash" - } - }, - { - "name": "format", - "in": "query", - "required": false, - "schema": { - "type": "string", - "default": "object", - "title": "Format" - } - }, - { - "name": "list_files", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "List Files" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/CheckCached" - }, - { - "type": "null" - } - ], - "title": "Hash List" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/webdl/hosters": { - "get": { - "tags": [ - "web downloads" - ], - "summary": "Get Hosters List", - "operationId": "get_hosters_list_v1_api_webdl_hosters_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - } - } - }, - "/v1/api/webdl/editwebdownload": { - "put": { - "tags": [ - "web downloads" - ], - "summary": "Edit Web Download", - "description": "Edits a web download's data. This overwrites the pre-existing data for the web download. Item must be cached. Requires API key.", - "operationId": "edit_web_download_v1_api_webdl_editwebdownload_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EditWebDownload" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/usenet/createusenetdownload": { - "post": { - "tags": [ - "usenet downloads" - ], - "summary": "Create Usenet Download", - "description": "Creates a usenet download from a link or nzb file. Requires API key.", - "operationId": "create_usenet_download_v1_api_usenet_createusenetdownload_post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_usenet_download_v1_api_usenet_createusenetdownload_post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/usenet/asynccreateusenetdownload": { - "post": { - "tags": [ - "usenet downloads" - ], - "summary": "Async Create Usenet Download", - "description": "Creates a usenet download from a link or nzb file. Requires API key.", - "operationId": "async_create_usenet_download_v1_api_usenet_asynccreateusenetdownload_post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_async_create_usenet_download_v1_api_usenet_asynccreateusenetdownload_post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/usenet/controlusenetdownload": { - "post": { - "tags": [ - "usenet downloads" - ], - "summary": "Control Usenet Download", - "description": "Controls a usenet download. Requires API key.", - "operationId": "control_usenet_download_v1_api_usenet_controlusenetdownload_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ControlUsenetDownload" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/usenet/requestdl": { - "get": { - "tags": [ - "usenet downloads" - ], - "summary": "Request Usenet Download", - "operationId": "request_usenet_download_v1_api_usenet_requestdl_get", - "parameters": [ - { - "name": "token", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "Token" - } - }, - { - "name": "usenet_id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "title": "Usenet Id" - } - }, - { - "name": "file_id", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 0, - "title": "File Id" - } - }, - { - "name": "zip_link", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Zip Link" - } - }, - { - "name": "user_ip", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "User Ip" - } - }, - { - "name": "redirect", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Redirect" - } - }, - { - "name": "append_name", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Append Name" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/usenet/mylist": { - "get": { - "tags": [ - "usenet downloads" - ], - "summary": "Get My Usenetdownloads List", - "operationId": "get_my_usenetdownloads_list_v1_api_usenet_mylist_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "id", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Id" - } - }, - { - "name": "bypass_cache", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Bypass Cache" - } - }, - { - "name": "offset", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 0, - "title": "Offset" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 1000, - "title": "Limit" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/usenet/checkcached": { - "get": { - "tags": [ - "usenet downloads" - ], - "summary": "Check Cached Usenetdownload", - "operationId": "check_cached_usenetdownload_v1_api_usenet_checkcached_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "hash", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "null" - } - ], - "title": "Hash" - } - }, - { - "name": "format", - "in": "query", - "required": false, - "schema": { - "type": "string", - "default": "object", - "title": "Format" - } - }, - { - "name": "list_files", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "List Files" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/CheckCached" - }, - { - "type": "null" - } - ], - "title": "Hash List" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - }, - "post": { - "tags": [ - "usenet downloads" - ], - "summary": "Check Cached Usenetdownload", - "operationId": "check_cached_usenetdownload_v1_api_usenet_checkcached_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "hash", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "null" - } - ], - "title": "Hash" - } - }, - { - "name": "format", - "in": "query", - "required": false, - "schema": { - "type": "string", - "default": "object", - "title": "Format" - } - }, - { - "name": "list_files", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "List Files" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/CheckCached" - }, - { - "type": "null" - } - ], - "title": "Hash List" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/usenet/editusenetdownload": { - "put": { - "tags": [ - "usenet downloads" - ], - "summary": "Edit Usenet Download", - "description": "Edits a usenet download's data. This overwrites the pre-existing data for the usenet download. Item must be cached. Requires API key.", - "operationId": "edit_usenet_download_v1_api_usenet_editusenetdownload_put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EditUsenetDownload" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/rss/addrss": { - "post": { - "tags": [ - "rss" - ], - "summary": "Add Rss Feed", - "description": "Adds a new RSS feed that the user can use to download torrents. Requires an API key.", - "operationId": "add_rss_feed_v1_api_rss_addrss_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddRss" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/rss/controlrss": { - "post": { - "tags": [ - "rss" - ], - "summary": "Control Rss Feed", - "description": "Controls an RSS feed. Requires an API key.", - "operationId": "control_rss_feed_v1_api_rss_controlrss_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ControlRss" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/rss/modifyrss": { - "post": { - "tags": [ - "rss" - ], - "summary": "Modify Rss Feed", - "description": "Modifies an RSS feed. Requires an API key.", - "operationId": "modify_rss_feed_v1_api_rss_modifyrss_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ModifyRss" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/rss/getfeeds": { - "get": { - "tags": [ - "rss" - ], - "summary": "Get Rss Feeds", - "description": "Gets all RSS feeds for a user. Requires an API key.", - "operationId": "get_rss_feeds_v1_api_rss_getfeeds_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "id", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/rss/getfeeditems": { - "get": { - "tags": [ - "rss" - ], - "summary": "Get Rss Feed Items", - "description": "Gets all RSS feeds for a user. Requires an API key.", - "operationId": "get_rss_feed_items_v1_api_rss_getfeeditems_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "rss_feed_id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "title": "Rss Feed Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/stream/createstream": { - "get": { - "tags": [ - "stream" - ], - "summary": "Create Stream", - "description": "Creates a stream from a TorBox item. Requires API key.", - "operationId": "create_stream_v1_api_stream_createstream_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "title": "Id" - } - }, - { - "name": "file_id", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 0, - "title": "File Id" - } - }, - { - "name": "type", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": "torrent", - "title": "Type" - } - }, - { - "name": "chosen_subtitle_index", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - }, - { - "type": "null" - } - ], - "default": "null", - "title": "Chosen Subtitle Index" - } - }, - { - "name": "chosen_audio_index", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "default": 0, - "title": "Chosen Audio Index" - } - }, - { - "name": "chosen_resolution_index", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - }, - { - "type": "null" - } - ], - "default": "null", - "title": "Chosen Resolution Index" - } - }, - { - "name": "scrobbling_enabled", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": true, - "title": "Scrobbling Enabled" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/stream/getstreamdata": { - "get": { - "tags": [ - "stream" - ], - "summary": "Get Stream Data", - "description": "Gets stream data using a file_token and presigned token. Requires API key.", - "operationId": "get_stream_data_v1_api_stream_getstreamdata_get", - "parameters": [ - { - "name": "token", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "Token" - } - }, - { - "name": "presigned_token", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "Presigned Token" - } - }, - { - "name": "chosen_subtitle_index", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - }, - { - "type": "null" - } - ], - "default": "null", - "title": "Chosen Subtitle Index" - } - }, - { - "name": "chosen_audio_index", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "default": 0, - "title": "Chosen Audio Index" - } - }, - { - "name": "chosen_resolution_index", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - }, - { - "type": "null" - } - ], - "default": "null", - "title": "Chosen Resolution Index" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/integration/oauth/me": { - "get": { - "tags": [ - "integration" - ], - "summary": "Get Oauth Integrations", - "description": "Get OAuth integrations for the authenticated user.", - "operationId": "get_oauth_integrations_v1_api_integration_oauth_me_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/integration/oauth/{provider}": { - "get": { - "tags": [ - "integration" - ], - "summary": "Oauth Redirect", - "description": "Redirects to the OAuth provider for authentication.", - "operationId": "oauth_redirect_v1_api_integration_oauth__provider__get", - "parameters": [ - { - "name": "provider", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Provider" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/integration/oauth/{provider}/callback": { - "get": { - "tags": [ - "integration" - ], - "summary": "Oauth Callback", - "description": "Callback URL for OAuth provider.", - "operationId": "oauth_callback_v1_api_integration_oauth__provider__callback_get", - "parameters": [ - { - "name": "provider", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Provider" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - }, - "post": { - "tags": [ - "integration" - ], - "summary": "Oauth Callback", - "description": "Callback URL for OAuth provider.", - "operationId": "oauth_callback_v1_api_integration_oauth__provider__callback_get", - "parameters": [ - { - "name": "provider", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Provider" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/integration/oauth/{provider}/success": { - "get": { - "tags": [ - "integration" - ], - "summary": "Oauth Success", - "description": "Provides token after successful OAuth authentication.", - "operationId": "oauth_success_v1_api_integration_oauth__provider__success_get", - "parameters": [ - { - "name": "provider", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Provider" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/integration/googledrive": { - "post": { - "tags": [ - "integration" - ], - "summary": "Add To Google Drive", - "description": "Uploads a file or zip to Google Drive. Requires API key and Google Drive Oauth token.", - "operationId": "add_to_google_drive_v1_api_integration_googledrive_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddToGoogleDrive" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/integration/dropbox": { - "post": { - "tags": [ - "integration" - ], - "summary": "Add To Dropbox", - "description": "Uploads a file or zip to Dropbox. Requires API key and Dropbox Oauth token.", - "operationId": "add_to_dropbox_v1_api_integration_dropbox_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddToDropbox" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/integration/onedrive": { - "post": { - "tags": [ - "integration" - ], - "summary": "Add To Onedrive", - "description": "Uploads a file or zip to Onedrive. Requires API key and Onedrive Oauth token.", - "operationId": "add_to_onedrive_v1_api_integration_onedrive_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddToOnedrive" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/integration/gofile": { - "post": { - "tags": [ - "integration" - ], - "summary": "Add To Gofile", - "description": "Uploads a file or zip to Gofile. Requires API key, and optional Gofile API token.", - "operationId": "add_to_gofile_v1_api_integration_gofile_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddToGofile" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/integration/1fichier": { - "post": { - "tags": [ - "integration" - ], - "summary": "Add To 1Fichier", - "description": "Uploads a file or zip to 1Fichier. Requires API key, and optional 1Fichier API token.", - "operationId": "add_to_1fichier_v1_api_integration_1fichier_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddTo1Fichier" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/integration/pixeldrain": { - "post": { - "tags": [ - "integration" - ], - "summary": "Add To Pixeldrain", - "description": "Uploads a file or zip to Pixeldrain.", - "operationId": "add_to_pixeldrain_v1_api_integration_pixeldrain_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddToPixeldrain" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/integration/job/{job_id}": { - "get": { - "tags": [ - "integration" - ], - "summary": "Get Job Status", - "description": "Get the status of a transfer.", - "operationId": "get_job_status_v1_api_integration_job__job_id__get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "job_id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "title": "Job Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - }, - "delete": { - "tags": [ - "integration" - ], - "summary": "Cancel Job", - "description": "Cancels a transfer job.", - "operationId": "cancel_job_v1_api_integration_job__job_id__delete", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "job_id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "title": "Job Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/integration/jobs": { - "get": { - "tags": [ - "integration" - ], - "summary": "Get All Jobs", - "description": "Get all active transfers.", - "operationId": "get_all_jobs_v1_api_integration_jobs_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/integration/jobs/{hash}": { - "get": { - "tags": [ - "integration" - ], - "summary": "Get Job By Hash", - "description": "Get all active transfers by hash.", - "operationId": "get_job_by_hash_v1_api_integration_jobs__hash__get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "hash", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Hash" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/integration/oauth/{provider}/register": { - "post": { - "tags": [ - "integration" - ], - "summary": "Oauth Register", - "description": "Registers a new OAuth link with the specified provider.", - "operationId": "oauth_register_v1_api_integration_oauth__provider__register_post", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "provider", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Provider" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OAuthRegisterRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/integration/oauth/{provider}/unregister": { - "delete": { - "tags": [ - "integration" - ], - "summary": "Oauth Unregister", - "description": "Unregisters an OAuth link with the specified provider.", - "operationId": "oauth_unregister_v1_api_integration_oauth__provider__unregister_delete", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "provider", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Provider" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/integration/oauth/discord/linked_roles": { - "post": { - "tags": [ - "integration" - ], - "summary": "Get Linked Discord Roles", - "operationId": "get_linked_discord_roles_v1_api_integration_oauth_discord_linked_roles_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LinkedRolesData" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/vendors/register": { - "post": { - "tags": [ - "vendors" - ], - "summary": "Register Vendor", - "description": "Registers a vendor account under the user's TorBox account.", - "operationId": "register_vendor_v1_api_vendors_register_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_register_vendor_v1_api_vendors_register_post" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/vendors/account": { - "get": { - "tags": [ - "vendors" - ], - "summary": "Get Vendor Account", - "description": "Retrieves the vendor account details for the user.", - "operationId": "get_vendor_account_v1_api_vendors_account_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/vendors/updateaccount": { - "put": { - "tags": [ - "vendors" - ], - "summary": "Update Vendor Account", - "description": "Updates the vendor account details for the user.", - "operationId": "update_vendor_account_v1_api_vendors_updateaccount_put", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_update_vendor_account_v1_api_vendors_updateaccount_put" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/vendors/getaccounts": { - "get": { - "tags": [ - "vendors" - ], - "summary": "Get Vendor Accounts", - "description": "Retrieves all user accounts owned by the vendor.", - "operationId": "get_vendor_accounts_v1_api_vendors_getaccounts_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/vendors/getaccount": { - "get": { - "tags": [ - "vendors" - ], - "summary": "Get User Vendor Account", - "description": "Retrieves a specific user account owned by the vendor.", - "operationId": "get_user_vendor_account_v1_api_vendors_getaccount_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "user_auth_id", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "User Auth Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/vendors/registeruser": { - "post": { - "tags": [ - "vendors" - ], - "summary": "Register New User", - "description": "Registers a new user account under the vendor.", - "operationId": "register_new_user_v1_api_vendors_registeruser_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_register_new_user_v1_api_vendors_registeruser_post" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/vendors/removeuser": { - "delete": { - "tags": [ - "vendors" - ], - "summary": "Delete User", - "description": "Removes account from the vendor. User must be owned by vendor. User account will not be deleted, only released from vendor.", - "operationId": "delete_user_v1_api_vendors_removeuser_delete", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "user_auth_id", - "in": "query", - "required": true, - "schema": { - "type": "string", - "title": "User Auth Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/vendors/refresh": { - "patch": { - "tags": [ - "vendors" - ], - "summary": "Refresh Vendor Users", - "description": "Refreshes the users under the vendor account to the latest plan and status.", - "operationId": "refresh_vendor_users_v1_api_vendors_refresh_patch", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - }, - "/v1/api/queued/getqueued": { - "get": { - "tags": [ - "queued" - ], - "summary": "Get Queued Downlodas", - "description": "Gets a list of queued downloads. Requires API key.", - "operationId": "get_queued_downlodas_v1_api_queued_getqueued_get", - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ], - "parameters": [ - { - "name": "id", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Id" - } - }, - { - "name": "type", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": "torrent", - "title": "Type" - } - }, - { - "name": "bypass_cache", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": false, - "title": "Bypass Cache" - } - }, - { - "name": "offset", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 0, - "title": "Offset" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "default": 1000, - "title": "Limit" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again.", - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/v1/api/queued/controlqueued": { - "post": { - "tags": [ - "queued" - ], - "summary": "Control Queued", - "description": "Controls a queued torrent. Requires API key.", - "operationId": "control_queued_v1_api_queued_controlqueued_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ControlQueuedDownload" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - } - } - } - }, - "404": { - "description": "Not Found", - "success": false, - "error": "ENDPOINT_NOT_FOUND", - "detail": "Endpoint not found. Please check the URL and try again." - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [ - ] - } - ] - } - } - }, - "components": { - "schemas": { - "AddRss": { - "properties": { - "url": { - "type": "string", - "title": "Url" - }, - "name": { - "type": "string", - "title": "Name" - }, - "do_regex": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Do Regex" - }, - "dont_regex": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Dont Regex" - }, - "dont_older_than": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Dont Older Than" - }, - "pass_check": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Pass Check", - "default": false - }, - "scan_interval": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Scan Interval", - "default": 60 - }, - "rss_type": { - "type": "string", - "title": "Rss Type", - "default": "torrent" - }, - "torrent_seeding": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Torrent Seeding", - "default": 1 - } - }, - "type": "object", - "required": [ - "url", - "name" - ], - "title": "AddRss" - }, - "AddTo1Fichier": { - "properties": { - "id": { - "type": "integer", - "title": "Id" - }, - "file_id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "File Id" - }, - "zip": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Zip", - "default": false - }, - "type": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Type", - "default": "torrent" - }, - "onefichier_token": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Onefichier Token" - } - }, - "type": "object", - "required": [ - "id" - ], - "title": "AddTo1Fichier" - }, - "AddToDropbox": { - "properties": { - "id": { - "type": "integer", - "title": "Id" - }, - "file_id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "File Id" - }, - "zip": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Zip", - "default": false - }, - "type": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Type", - "default": "torrent" - }, - "dropbox_token": { - "type": "string", - "title": "Dropbox Token" - } - }, - "type": "object", - "required": [ - "id", - "dropbox_token" - ], - "title": "AddToDropbox" - }, - "AddToGofile": { - "properties": { - "id": { - "type": "integer", - "title": "Id" - }, - "file_id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "File Id" - }, - "zip": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Zip", - "default": false - }, - "type": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Type", - "default": "torrent" - }, - "gofile_token": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Gofile Token" - } - }, - "type": "object", - "required": [ - "id" - ], - "title": "AddToGofile" - }, - "AddToGoogleDrive": { - "properties": { - "id": { - "type": "integer", - "title": "Id" - }, - "file_id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "File Id" - }, - "zip": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Zip", - "default": false - }, - "type": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Type", - "default": "torrent" - }, - "google_token": { - "type": "string", - "title": "Google Token" - } - }, - "type": "object", - "required": [ - "id", - "google_token" - ], - "title": "AddToGoogleDrive" - }, - "AddToOnedrive": { - "properties": { - "id": { - "type": "integer", - "title": "Id" - }, - "file_id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "File Id" - }, - "zip": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Zip", - "default": false - }, - "type": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Type", - "default": "torrent" - }, - "onedrive_token": { - "type": "string", - "title": "Onedrive Token" - } - }, - "type": "object", - "required": [ - "id", - "onedrive_token" - ], - "title": "AddToOnedrive" - }, - "AddToPixeldrain": { - "properties": { - "id": { - "type": "integer", - "title": "Id" - }, - "file_id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "File Id" - }, - "zip": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Zip", - "default": false - }, - "type": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Type", - "default": "torrent" - }, - "pixeldrain_token": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Pixeldrain Token" - } - }, - "type": "object", - "required": [ - "id" - ], - "title": "AddToPixeldrain" - }, - "BaseSettingsModel": { - "properties": { - "email_notifications": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Email Notifications" - }, - "web_notifications": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Web Notifications" - }, - "mobile_notifications": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Mobile Notifications" - }, - "rss_notifications": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Rss Notifications" - }, - "download_speed_in_tab": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Download Speed In Tab" - }, - "show_tracker_in_torrents": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Show Tracker In Torrents" - }, - "stremio_quality": { - "anyOf": [ - { - "items": { - "type": "integer" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Stremio Quality" - }, - "stremio_resolution": { - "anyOf": [ - { - "items": { - "type": "integer" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Stremio Resolution" - }, - "stremio_language": { - "anyOf": [ - { - "items": { - "type": "integer" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Stremio Language" - }, - "stremio_cache": { - "anyOf": [ - { - "items": { - "type": "integer" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Stremio Cache" - }, - "stremio_size_lower": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Stremio Size Lower" - }, - "google_drive_folder_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Google Drive Folder Id" - }, - "onedrive_save_path": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Onedrive Save Path" - }, - "discord_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Discord Id" - }, - "discord_notifications": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Discord Notifications" - }, - "stremio_allow_adult": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Stremio Allow Adult" - }, - "stremio_seed_torrents": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Stremio Seed Torrents" - }, - "seed_torrents": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Seed Torrents" - }, - "allow_zipped": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Allow Zipped" - }, - "stremio_allow_zipped": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Stremio Allow Zipped" - }, - "onefichier_folder_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Onefichier Folder Id" - }, - "gofile_folder_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Gofile Folder Id" - }, - "jdownloader_notifications": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Jdownloader Notifications" - }, - "webhook_notifications": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Webhook Notifications" - }, - "webhook_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Webhook Url" - }, - "telegram_notifications": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Telegram Notifications" - }, - "telegram_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Telegram Id" - }, - "mega_email": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Mega Email" - }, - "mega_password": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Mega Password" - }, - "stremio_sort": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Stremio Sort" - }, - "cdn_selection": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Cdn Selection" - }, - "stremio_use_custom_search_engines": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Stremio Use Custom Search Engines" - }, - "stremio_result_sort": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Stremio Result Sort" - }, - "webdav_use_local_files": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Webdav Use Local Files" - }, - "stremio_legacy_your_media": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Stremio Legacy Your Media" - }, - "stremio_only_your_media_streams": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Stremio Only Your Media Streams" - }, - "stremio_disable_your_media_streams": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Stremio Disable Your Media Streams" - }, - "webdav_use_folder_view": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Webdav Use Folder View" - }, - "webdav_flatten": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Webdav Flatten" - }, - "stremio_limit_per_resolution_torrent": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Stremio Limit Per Resolution Torrent" - }, - "stremio_limit_per_resolution_usenet": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Stremio Limit Per Resolution Usenet" - }, - "stremio_size_upper": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Stremio Size Upper" - }, - "stremio_torrent_seeders_cutoff": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Stremio Torrent Seeders Cutoff" - }, - "pixeldrain_api_key": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Pixeldrain Api Key" - }, - "onefichier_api_key": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Onefichier Api Key" - }, - "gofile_api_key": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Gofile Api Key" - }, - "patreon_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Patreon Id" - }, - "stremio_wait_for_download_usenet": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Stremio Wait For Download Usenet" - }, - "stremio_wait_for_download_torrent": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Stremio Wait For Download Torrent" - }, - "stremio_disable_filtered_note": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Stremio Disable Filtered Note" - }, - "stremio_emoji_in_description": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Stremio Emoji In Description" - }, - "dashboard_filter": { - "anyOf": [ - { - "additionalProperties": true, - "type": "object" - }, - { - "type": "null" - } - ], - "title": "Dashboard Filter" - }, - "web_player_always_transcode": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Web Player Always Transcode" - }, - "web_player_always_skip_intro": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Web Player Always Skip Intro" - }, - "web_player_audio_preferred_language": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Web Player Audio Preferred Language" - }, - "web_player_subtitle_preferred_language": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Web Player Subtitle Preferred Language" - }, - "web_player_disable_prestream_selector": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Web Player Disable Prestream Selector" - }, - "dashboard_sort": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Dashboard Sort" - }, - "web_player_disable_next_up_dialogue": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Web Player Disable Next Up Dialogue" - }, - "web_player_enable_scrobbling": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Web Player Enable Scrobbling" - }, - "append_filename_to_links": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Append Filename To Links" - } - }, - "type": "object", - "title": "BaseSettingsModel" - }, - "Body_async_create_torrent_v1_api_torrents_asynccreatetorrent_post": { - "properties": { - "file": { - "anyOf": [ - { - "type": "string", - "contentMediaType": "application/octet-stream" - }, - { - "type": "null" - } - ], - "title": "File" - }, - "magnet": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Magnet" - }, - "seed": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Seed" - }, - "allow_zip": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Allow Zip", - "default": true - }, - "name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Name" - }, - "as_queued": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "As Queued", - "default": false - }, - "add_only_if_cached": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Add Only If Cached", - "default": false - } - }, - "type": "object", - "title": "Body_async_create_torrent_v1_api_torrents_asynccreatetorrent_post" - }, - "Body_async_create_usenet_download_v1_api_usenet_asynccreateusenetdownload_post": { - "properties": { - "link": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Link" - }, - "file": { - "anyOf": [ - { - "type": "string", - "contentMediaType": "application/octet-stream" - }, - { - "type": "null" - } - ], - "title": "File" - }, - "name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Name" - }, - "password": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Password" - }, - "post_processing": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Post Processing", - "default": -1 - }, - "as_queued": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "As Queued", - "default": false - }, - "add_only_if_cached": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Add Only If Cached", - "default": false - } - }, - "type": "object", - "title": "Body_async_create_usenet_download_v1_api_usenet_asynccreateusenetdownload_post" - }, - "Body_async_create_web_download_v1_api_webdl_asynccreatewebdownload_post": { - "properties": { - "link": { - "type": "string", - "title": "Link" - }, - "password": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Password" - }, - "name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Name" - }, - "as_queued": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "As Queued", - "default": false - }, - "add_only_if_cached": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Add Only If Cached", - "default": false - } - }, - "type": "object", - "required": [ - "link" - ], - "title": "Body_async_create_web_download_v1_api_webdl_asynccreatewebdownload_post" - }, - "Body_create_torrent_v1_api_torrents_createtorrent_post": { - "properties": { - "file": { - "anyOf": [ - { - "type": "string", - "contentMediaType": "application/octet-stream" - }, - { - "type": "null" - } - ], - "title": "File" - }, - "magnet": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Magnet" - }, - "seed": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Seed" - }, - "allow_zip": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Allow Zip", - "default": true - }, - "name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Name" - }, - "as_queued": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "As Queued", - "default": false - }, - "add_only_if_cached": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Add Only If Cached", - "default": false - } - }, - "type": "object", - "title": "Body_create_torrent_v1_api_torrents_createtorrent_post" - }, - "Body_create_usenet_download_v1_api_usenet_createusenetdownload_post": { - "properties": { - "link": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Link" - }, - "file": { - "anyOf": [ - { - "type": "string", - "contentMediaType": "application/octet-stream" - }, - { - "type": "null" - } - ], - "title": "File" - }, - "name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Name" - }, - "password": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Password" - }, - "post_processing": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Post Processing", - "default": -1 - }, - "as_queued": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "As Queued", - "default": false - }, - "add_only_if_cached": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Add Only If Cached", - "default": false - } - }, - "type": "object", - "title": "Body_create_usenet_download_v1_api_usenet_createusenetdownload_post" - }, - "Body_create_web_download_v1_api_webdl_createwebdownload_post": { - "properties": { - "link": { - "type": "string", - "title": "Link" - }, - "password": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Password" - }, - "name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Name" - }, - "as_queued": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "As Queued", - "default": false - }, - "add_only_if_cached": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Add Only If Cached", - "default": false - } - }, - "type": "object", - "required": [ - "link" - ], - "title": "Body_create_web_download_v1_api_webdl_createwebdownload_post" - }, - "Body_get_torrent_info_post_v1_api_torrents_torrentinfo_post": { - "properties": { - "timeout": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Timeout", - "default": 10 - }, - "hash": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Hash" - }, - "magnet": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Magnet" - }, - "file": { - "anyOf": [ - { - "type": "string", - "contentMediaType": "application/octet-stream" - }, - { - "type": "null" - } - ], - "title": "File" - }, - "use_cache_lookup": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Use Cache Lookup", - "default": false - }, - "peers_only": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Peers Only", - "default": false - } - }, - "type": "object", - "title": "Body_get_torrent_info_post_v1_api_torrents_torrentinfo_post" - }, - "Body_register_new_user_v1_api_vendors_registeruser_post": { - "properties": { - "user_email": { - "type": "string", - "title": "User Email" - } - }, - "type": "object", - "required": [ - "user_email" - ], - "title": "Body_register_new_user_v1_api_vendors_registeruser_post" - }, - "Body_register_vendor_v1_api_vendors_register_post": { - "properties": { - "vendor_name": { - "type": "string", - "title": "Vendor Name" - }, - "vendor_url": { - "type": "string", - "title": "Vendor Url" - } - }, - "type": "object", - "required": [ - "vendor_name", - "vendor_url" - ], - "title": "Body_register_vendor_v1_api_vendors_register_post" - }, - "Body_update_vendor_account_v1_api_vendors_updateaccount_put": { - "properties": { - "vendor_name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Vendor Name" - }, - "vendor_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Vendor Url" - } - }, - "type": "object", - "title": "Body_update_vendor_account_v1_api_vendors_updateaccount_put" - }, - "CheckCached": { - "properties": { - "hashes": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Hashes" - } - }, - "type": "object", - "required": [ - "hashes" - ], - "title": "CheckCached" - }, - "ControlQueuedDownload": { - "properties": { - "operation": { - "type": "string", - "title": "Operation" - }, - "queued_id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Queued Id" - }, - "all": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "All", - "default": false - } - }, - "type": "object", - "required": [ - "operation" - ], - "title": "ControlQueuedDownload" - }, - "ControlRss": { - "properties": { - "operation": { - "type": "string", - "title": "Operation" - }, - "rss_feed_id": { - "type": "integer", - "title": "Rss Feed Id" - } - }, - "type": "object", - "required": [ - "operation", - "rss_feed_id" - ], - "title": "ControlRss" - }, - "ControlSearchEngine": { - "properties": { - "operation": { - "$ref": "#/components/schemas/ControlSearchEngineOperation" - }, - "id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Id" - }, - "all": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "All", - "default": false - } - }, - "type": "object", - "required": [ - "operation" - ], - "title": "ControlSearchEngine" - }, - "ControlSearchEngineOperation": { - "type": "string", - "enum": [ - "delete", - "enable", - "disable", - "check" - ], - "title": "ControlSearchEngineOperation" - }, - "ControlTorrent": { - "properties": { - "operation": { - "type": "string", - "title": "Operation" - }, - "torrent_id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Torrent Id" - }, - "all": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "All", - "default": false - } - }, - "type": "object", - "required": [ - "operation" - ], - "title": "ControlTorrent" - }, - "ControlUsenetDownload": { - "properties": { - "operation": { - "type": "string", - "title": "Operation" - }, - "usenet_id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Usenet Id" - }, - "all": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "All", - "default": false - } - }, - "type": "object", - "required": [ - "operation" - ], - "title": "ControlUsenetDownload" - }, - "ControlWebDownload": { - "properties": { - "operation": { - "type": "string", - "title": "Operation" - }, - "webdl_id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Webdl Id" - }, - "all": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "All", - "default": false - } - }, - "type": "object", - "required": [ - "operation" - ], - "title": "ControlWebDownload" - }, - "DeleteAccountSchema": { - "properties": { - "session_token": { - "type": "string", - "title": "Session Token" - }, - "confirmation_code": { - "type": "integer", - "title": "Confirmation Code" - } - }, - "type": "object", - "required": [ - "session_token", - "confirmation_code" - ], - "title": "DeleteAccountSchema" - }, - "DeviceTokenSchema": { - "properties": { - "device_code": { - "type": "string", - "title": "Device Code" - } - }, - "type": "object", - "required": [ - "device_code" - ], - "title": "DeviceTokenSchema" - }, - "EditTorrent": { - "properties": { - "torrent_id": { - "type": "integer", - "title": "Torrent Id" - }, - "name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Name" - }, - "tags": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Tags" - }, - "alternative_hashes": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Alternative Hashes" - } - }, - "type": "object", - "required": [ - "torrent_id" - ], - "title": "EditTorrent" - }, - "EditUsenetDownload": { - "properties": { - "usenet_download_id": { - "type": "integer", - "title": "Usenet Download Id" - }, - "name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Name" - }, - "tags": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Tags" - }, - "alternative_hashes": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Alternative Hashes" - } - }, - "type": "object", - "required": [ - "usenet_download_id" - ], - "title": "EditUsenetDownload" - }, - "EditWebDownload": { - "properties": { - "webdl_id": { - "type": "integer", - "title": "Webdl Id" - }, - "name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Name" - }, - "tags": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Tags" - }, - "alternative_hashes": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Alternative Hashes" - } - }, - "type": "object", - "required": [ - "webdl_id" - ], - "title": "EditWebDownload" - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail" - } - }, - "type": "object", - "title": "HTTPValidationError" - }, - "LinkedRolesData": { - "properties": { - "discord_token": { - "type": "string", - "title": "Discord Token" - } - }, - "type": "object", - "required": [ - "discord_token" - ], - "title": "LinkedRolesData" - }, - "MagnetToTorrent": { - "properties": { - "magnet": { - "type": "string", - "title": "Magnet" - } - }, - "type": "object", - "required": [ - "magnet" - ], - "title": "MagnetToTorrent" - }, - "ModifyRss": { - "properties": { - "rss_feed_id": { - "type": "integer", - "title": "Rss Feed Id" - }, - "name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Name" - }, - "do_regex": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Do Regex" - }, - "dont_regex": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Dont Regex" - }, - "scan_interval": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Scan Interval", - "default": 60 - }, - "dont_older_than": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Dont Older Than" - }, - "rss_type": { - "type": "string", - "title": "Rss Type", - "default": [ - "torrent" - ] - }, - "torrent_seeding": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Torrent Seeding", - "default": 1 - } - }, - "type": "object", - "required": [ - "rss_feed_id" - ], - "title": "ModifyRss" - }, - "OAuthRegisterRequest": { - "properties": { - "token": { - "type": "string", - "title": "Token" - }, - "refresh_token": { - "type": "string", - "title": "Refresh Token" - } - }, - "type": "object", - "required": [ - "token", - "refresh_token" - ], - "title": "OAuthRegisterRequest" - }, - "RefreshTokenSchema": { - "properties": { - "session_token": { - "type": "string", - "title": "Session Token" - } - }, - "type": "object", - "required": [ - "session_token" - ], - "title": "RefreshTokenSchema" - }, - "SearchEngineDownloadType": { - "type": "string", - "enum": [ - "torrents", - "usenet" - ], - "title": "SearchEngineDownloadType" - }, - "SearchEngineEditModel": { - "properties": { - "id": { - "type": "integer", - "title": "Id" - }, - "type": { - "anyOf": [ - { - "$ref": "#/components/schemas/SearchEngineType" - }, - { - "type": "null" - } - ] - }, - "url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Url" - }, - "apikey": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Apikey" - }, - "download_type": { - "anyOf": [ - { - "$ref": "#/components/schemas/SearchEngineDownloadType" - }, - { - "type": "null" - } - ] - } - }, - "type": "object", - "required": [ - "id" - ], - "title": "SearchEngineEditModel" - }, - "SearchEngineModel": { - "properties": { - "type": { - "$ref": "#/components/schemas/SearchEngineType" - }, - "url": { - "type": "string", - "title": "Url" - }, - "apikey": { - "type": "string", - "title": "Apikey" - }, - "download_type": { - "$ref": "#/components/schemas/SearchEngineDownloadType" - } - }, - "type": "object", - "required": [ - "type", - "url", - "apikey", - "download_type" - ], - "title": "SearchEngineModel" - }, - "SearchEngineType": { - "type": "string", - "enum": [ - "prowlarr", - "jackett", - "nzbhydra" - ], - "title": "SearchEngineType" - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - }, - "type": "array", - "title": "Location" - }, - "msg": { - "type": "string", - "title": "Message" - }, - "type": { - "type": "string", - "title": "Error Type" - }, - "input": { - "title": "Input" - }, - "ctx": { - "type": "object", - "title": "Context" - } - }, - "type": "object", - "required": [ - "loc", - "msg", - "type" - ], - "title": "ValidationError" - } - }, - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": { - "password": { - "scopes": { - }, - "tokenUrl": "token" - } - } - } - } - } -} \ No newline at end of file diff --git a/src/TorBoxSDK.Examples/Main/Notifications/NotificationsExample.cs b/src/TorBoxSDK.Examples/Main/Notifications/NotificationsExample.cs index 80a32c0..92aeea1 100644 --- a/src/TorBoxSDK.Examples/Main/Notifications/NotificationsExample.cs +++ b/src/TorBoxSDK.Examples/Main/Notifications/NotificationsExample.cs @@ -114,7 +114,7 @@ public static async Task RunAsync() Console.WriteLine("Fetching Intercom identity hash..."); TorBoxResponse intercomResponse = - await client.Main.Notifications.GetIntercomHashAsync(new GetIntercomHashOptions { AuthId = "your-auth-id", Email = "your@email.com" }, cts.Token); + await client.Main.Notifications.GetIntercomHashAsync("your-auth-id", "your@email.com", cts.Token); if (intercomResponse.Data is not null) { diff --git a/src/TorBoxSDK.Examples/Main/Stream/StreamExample.cs b/src/TorBoxSDK.Examples/Main/Stream/StreamExample.cs index 5df04b4..c9085f7 100644 --- a/src/TorBoxSDK.Examples/Main/Stream/StreamExample.cs +++ b/src/TorBoxSDK.Examples/Main/Stream/StreamExample.cs @@ -34,7 +34,7 @@ public static async Task RunAsync() TorBoxResponse streamResponse = await client.Main.Stream.CreateStreamAsync( - new CreateStreamOptions { Id = downloadId, FileId = fileId, Type = type }, + downloadId, fileId, type, cancellationToken: cts.Token); if (streamResponse.Data is not null) @@ -56,11 +56,9 @@ await client.Main.Stream.CreateStreamAsync( TorBoxResponse customStreamResponse = await client.Main.Stream.CreateStreamAsync( + downloadId, fileId, type, new CreateStreamOptions { - Id = downloadId, - FileId = fileId, - Type = type, ChosenSubtitleIndex = subtitleIndex, ChosenAudioIndex = audioIndex, }, @@ -89,10 +87,9 @@ await client.Main.Stream.CreateStreamAsync( TorBoxResponse streamDataResponse = await client.Main.Stream.GetStreamDataAsync( + presignedToken, authToken, new GetStreamDataOptions { - PresignedToken = presignedToken, - Token = authToken, ChosenSubtitleIndex = subtitleIndex, ChosenAudioIndex = audioIndex, }, diff --git a/src/TorBoxSDK.Examples/Main/Torrents/CheckCachedExample.cs b/src/TorBoxSDK.Examples/Main/Torrents/CheckCachedExample.cs index 02b53c7..c78fb79 100644 --- a/src/TorBoxSDK.Examples/Main/Torrents/CheckCachedExample.cs +++ b/src/TorBoxSDK.Examples/Main/Torrents/CheckCachedExample.cs @@ -32,7 +32,7 @@ public static async Task RunAsync() Console.WriteLine($"Checking cache for {hashes.Count} hash(es) via GET..."); TorBoxResponse cacheResponse = - await client.Main.Torrents.CheckCachedAsync(new CheckCachedOptions { Hashes = hashes }, cancellationToken: cts.Token); + await client.Main.Torrents.CheckCachedAsync(hashes, cancellationToken: cts.Token); Console.WriteLine($" Response: {cacheResponse.Detail ?? "Check completed"}"); @@ -62,7 +62,7 @@ public static async Task RunAsync() Console.WriteLine($"Getting torrent info for hash: {torrentHash}..."); TorBoxResponse infoResponse = - await client.Main.Torrents.GetTorrentInfoAsync(new GetTorrentInfoOptions { Hash = torrentHash }, cancellationToken: cts.Token); + await client.Main.Torrents.GetTorrentInfoAsync(torrentHash, cancellationToken: cts.Token); if (infoResponse.Data is not null) { diff --git a/src/TorBoxSDK.Examples/Main/Torrents/DownloadTorrentExample.cs b/src/TorBoxSDK.Examples/Main/Torrents/DownloadTorrentExample.cs index f0e8f01..755dc8f 100644 --- a/src/TorBoxSDK.Examples/Main/Torrents/DownloadTorrentExample.cs +++ b/src/TorBoxSDK.Examples/Main/Torrents/DownloadTorrentExample.cs @@ -25,13 +25,8 @@ public static async Task RunAsync() // ────────────────────────────────────────────────────── Console.WriteLine($"Requesting download link for torrent {torrentId}..."); - RequestDownloadOptions downloadOptions = new() - { - TorrentId = torrentId, - }; - TorBoxResponse downloadResponse = - await client.Main.Torrents.RequestDownloadAsync(downloadOptions, cts.Token); + await client.Main.Torrents.RequestDownloadAsync(torrentId, cancellationToken: cts.Token); if (downloadResponse.Data is not null) { @@ -46,12 +41,11 @@ public static async Task RunAsync() RequestDownloadOptions fileDownloadOptions = new() { - TorrentId = torrentId, FileId = fileId, }; TorBoxResponse fileDownloadResponse = - await client.Main.Torrents.RequestDownloadAsync(fileDownloadOptions, cts.Token); + await client.Main.Torrents.RequestDownloadAsync(torrentId, fileDownloadOptions, cts.Token); if (fileDownloadResponse.Data is not null) { @@ -63,12 +57,11 @@ public static async Task RunAsync() // ────────────────────────────────────────────────────── RequestDownloadOptions zipOptions = new() { - TorrentId = torrentId, ZipLink = true, }; TorBoxResponse zipResponse = - await client.Main.Torrents.RequestDownloadAsync(zipOptions, cts.Token); + await client.Main.Torrents.RequestDownloadAsync(torrentId, zipOptions, cts.Token); if (zipResponse.Data is not null) { @@ -81,7 +74,7 @@ public static async Task RunAsync() Console.WriteLine($"Exporting data for torrent {torrentId}..."); TorBoxResponse exportResponse = - await client.Main.Torrents.ExportDataAsync(new ExportDataOptions { TorrentId = torrentId }, cancellationToken: cts.Token); + await client.Main.Torrents.ExportDataAsync(torrentId, cancellationToken: cts.Token); if (exportResponse.Data is not null) { diff --git a/src/TorBoxSDK.Examples/Main/Usenet/CreateUsenetExample.cs b/src/TorBoxSDK.Examples/Main/Usenet/CreateUsenetExample.cs index fb655a5..9c2998e 100644 --- a/src/TorBoxSDK.Examples/Main/Usenet/CreateUsenetExample.cs +++ b/src/TorBoxSDK.Examples/Main/Usenet/CreateUsenetExample.cs @@ -76,13 +76,8 @@ public static async Task RunAsync() Console.WriteLine($"Requesting download link for Usenet ID {usenetId}..."); - RequestUsenetDownloadOptions downloadOptions = new() - { - UsenetId = usenetId, - }; - TorBoxResponse downloadResponse = - await client.Main.Usenet.RequestDownloadAsync(downloadOptions, cts.Token); + await client.Main.Usenet.RequestDownloadAsync(usenetId, cancellationToken: cts.Token); if (downloadResponse.Data is not null) { diff --git a/src/TorBoxSDK.Examples/Main/Usenet/UsenetAdvancedExample.cs b/src/TorBoxSDK.Examples/Main/Usenet/UsenetAdvancedExample.cs index a29a51e..0058b5d 100644 --- a/src/TorBoxSDK.Examples/Main/Usenet/UsenetAdvancedExample.cs +++ b/src/TorBoxSDK.Examples/Main/Usenet/UsenetAdvancedExample.cs @@ -26,7 +26,7 @@ public static async Task RunAsync() Console.WriteLine($"Checking cache for {hashes.Count} Usenet hash(es) (GET)..."); TorBoxResponse cacheResponse = - await client.Main.Usenet.CheckCachedAsync(new CheckCachedOptions { Hashes = hashes, Format = "object", ListFiles = true }, cancellationToken: cts.Token); + await client.Main.Usenet.CheckCachedAsync(hashes, new CheckCachedOptions { Format = "object", ListFiles = true }, cancellationToken: cts.Token); Console.WriteLine($" Cache check result: {cacheResponse.Detail ?? "Completed"}"); diff --git a/src/TorBoxSDK.Examples/Main/WebDownloads/CreateWebDownloadExample.cs b/src/TorBoxSDK.Examples/Main/WebDownloads/CreateWebDownloadExample.cs index ffcefc8..236b389 100644 --- a/src/TorBoxSDK.Examples/Main/WebDownloads/CreateWebDownloadExample.cs +++ b/src/TorBoxSDK.Examples/Main/WebDownloads/CreateWebDownloadExample.cs @@ -73,13 +73,8 @@ public static async Task RunAsync() Console.WriteLine($"Requesting download link for web download {webDownloadId}..."); - RequestWebDownloadOptions downloadOptions = new() - { - WebId = webDownloadId, - }; - TorBoxResponse downloadResponse = - await client.Main.WebDownloads.RequestDownloadAsync(downloadOptions, cts.Token); + await client.Main.WebDownloads.RequestDownloadAsync(webDownloadId, cancellationToken: cts.Token); if (downloadResponse.Data is not null) { diff --git a/src/TorBoxSDK.Examples/Main/WebDownloads/WebDownloadsAdvancedExample.cs b/src/TorBoxSDK.Examples/Main/WebDownloads/WebDownloadsAdvancedExample.cs index f4e36ee..ac30741 100644 --- a/src/TorBoxSDK.Examples/Main/WebDownloads/WebDownloadsAdvancedExample.cs +++ b/src/TorBoxSDK.Examples/Main/WebDownloads/WebDownloadsAdvancedExample.cs @@ -26,7 +26,7 @@ public static async Task RunAsync() Console.WriteLine($"Checking cache for {hashes.Count} web download hash(es) (GET)..."); TorBoxResponse cacheResponse = - await client.Main.WebDownloads.CheckCachedAsync(new CheckCachedOptions { Hashes = hashes, Format = "object", ListFiles = true }, cancellationToken: cts.Token); + await client.Main.WebDownloads.CheckCachedAsync(hashes, new CheckCachedOptions { Format = "object", ListFiles = true }, cancellationToken: cts.Token); Console.WriteLine($" Cache check result: {cacheResponse.Detail ?? "Completed"}"); diff --git a/src/TorBoxSDK.Examples/Relay/RelayExample.cs b/src/TorBoxSDK.Examples/Relay/RelayExample.cs index 6d3805e..3a687b0 100644 --- a/src/TorBoxSDK.Examples/Relay/RelayExample.cs +++ b/src/TorBoxSDK.Examples/Relay/RelayExample.cs @@ -24,8 +24,7 @@ public static async Task RunAsync() // ────────────────────────────────────────────────────── Console.WriteLine("Checking relay server status..."); - TorBoxResponse statusResponse = - await client.Relay.GetStatusAsync(cts.Token); + TorBoxResponse statusResponse = await client.Relay.GetStatusAsync(cts.Token); if (statusResponse.Data is not null) { @@ -48,7 +47,7 @@ public static async Task RunAsync() Console.WriteLine($"Checking inactivity for torrent {torrentId} (auth: {authId})..."); TorBoxResponse inactiveResponse = - await client.Relay.CheckForInactiveAsync(new CheckInactiveOptions { AuthId = authId, TorrentId = torrentId }, cts.Token); + await client.Relay.CheckForInactiveAsync(authId, torrentId, cts.Token); if (inactiveResponse.Data is not null) { diff --git a/src/TorBoxSDK.Examples/Search/DownloadSearchResultsExample.cs b/src/TorBoxSDK.Examples/Search/DownloadSearchResultsExample.cs index 4d2ef0f..c895a4e 100644 --- a/src/TorBoxSDK.Examples/Search/DownloadSearchResultsExample.cs +++ b/src/TorBoxSDK.Examples/Search/DownloadSearchResultsExample.cs @@ -61,7 +61,7 @@ public static async Task RunAsync() Console.WriteLine($"Downloading NZB for result: {resultId}..."); TorBoxResponse downloadResponse = - await client.Search.DownloadUsenetAsync(new DownloadUsenetOptions { Id = resultId, Guid = guid }, cts.Token); + await client.Search.DownloadUsenetAsync(resultId, guid, cts.Token); if (downloadResponse.Data is not null) { diff --git a/src/TorBoxSDK.Examples/Search/SearchMetaExample.cs b/src/TorBoxSDK.Examples/Search/SearchMetaExample.cs index ba63600..50a1ce6 100644 --- a/src/TorBoxSDK.Examples/Search/SearchMetaExample.cs +++ b/src/TorBoxSDK.Examples/Search/SearchMetaExample.cs @@ -64,7 +64,7 @@ public static async Task RunAsync() Console.WriteLine("Using Torznab search..."); TorBoxResponse torznabResponse = - await client.Search.SearchTorznabAsync(new SearchTorznabOptions { Query = query }, cancellationToken: cts.Token); + await client.Search.SearchTorznabAsync(query, cancellationToken: cts.Token); if (torznabResponse.Data is not null) { @@ -74,7 +74,7 @@ public static async Task RunAsync() Console.WriteLine("Using Newznab search..."); TorBoxResponse newznabResponse = - await client.Search.SearchNewznabAsync(new SearchNewznabOptions { Query = query }, cancellationToken: cts.Token); + await client.Search.SearchNewznabAsync(query, cancellationToken: cts.Token); if (newznabResponse.Data is not null) { diff --git a/src/TorBoxSDK/DependencyInjection/TorBoxServiceCollectionExtensions.cs b/src/TorBoxSDK/DependencyInjection/TorBoxServiceCollectionExtensions.cs index 7ff13d2..c80d608 100644 --- a/src/TorBoxSDK/DependencyInjection/TorBoxServiceCollectionExtensions.cs +++ b/src/TorBoxSDK/DependencyInjection/TorBoxServiceCollectionExtensions.cs @@ -8,8 +8,7 @@ namespace TorBoxSDK.DependencyInjection; /// -/// Extension methods for registering the TorBox SDK services -/// with a . +/// Extension methods for registering the TorBox SDK services with a . /// public static class TorBoxServiceCollectionExtensions { @@ -23,9 +22,7 @@ public static class TorBoxServiceCollectionExtensions /// /// Thrown when or is . /// - public static IServiceCollection AddTorBox( - this IServiceCollection services, - Action configure) + public static IServiceCollection AddTorBox(this IServiceCollection services, Action configure) { Guard.ThrowIfNull(services); Guard.ThrowIfNull(configure); @@ -52,9 +49,7 @@ public static IServiceCollection AddTorBox( /// /// Thrown when or is . /// - public static IServiceCollection AddTorBox( - this IServiceCollection services, - IConfiguration configuration) + public static IServiceCollection AddTorBox(this IServiceCollection services, IConfiguration configuration) { Guard.ThrowIfNull(services); Guard.ThrowIfNull(configuration); diff --git a/src/TorBoxSDK/Http/Handlers/AuthHandler.cs b/src/TorBoxSDK/Http/Handlers/AuthHandler.cs index 1b4ca5d..bfe5b83 100644 --- a/src/TorBoxSDK/Http/Handlers/AuthHandler.cs +++ b/src/TorBoxSDK/Http/Handlers/AuthHandler.cs @@ -1,3 +1,4 @@ +using System.Net.Http.Headers; using Microsoft.Extensions.Options; using TorBoxSDK.Http.Validation; @@ -24,7 +25,7 @@ protected override Task SendAsync( if (!string.IsNullOrWhiteSpace(apiKey)) { - request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", apiKey); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); } return base.SendAsync(request, cancellationToken); diff --git a/src/TorBoxSDK/Http/Json/SnakeCaseEnumConverter.cs b/src/TorBoxSDK/Http/Json/SnakeCaseEnumConverter.cs new file mode 100644 index 0000000..d9c017d --- /dev/null +++ b/src/TorBoxSDK/Http/Json/SnakeCaseEnumConverter.cs @@ -0,0 +1,169 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace TorBoxSDK.Http.Json; + +/// +/// A that serializes and deserializes enum values +/// as snake_case strings, working correctly on all supported .NET versions. +/// +/// +/// On .NET 6, with a naming policy only applies +/// the policy during serialization, not deserialization. This converter builds an explicit +/// lookup dictionary so that deserialization works correctly on all targets. +/// +internal sealed class SnakeCaseEnumConverterFactory : JsonConverterFactory +{ + private readonly JsonNamingPolicy _namingPolicy; + + internal SnakeCaseEnumConverterFactory(JsonNamingPolicy namingPolicy) + { + _namingPolicy = namingPolicy; + } + + public override bool CanConvert(Type typeToConvert) => + typeToConvert.IsEnum; + + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + Type converterType = typeof(SnakeCaseEnumConverter<>).MakeGenericType(typeToConvert); + return (JsonConverter?)Activator.CreateInstance(converterType, _namingPolicy); + } +} + +internal sealed class SnakeCaseEnumConverter : JsonConverter + where T : struct, Enum +{ + private readonly JsonNamingPolicy _namingPolicy; + + // Maps converted snake_case name → enum value (for reading) + private readonly Dictionary _readMap; + + // Maps enum value → converted snake_case name (for writing) + private readonly Dictionary _writeMap; + + public SnakeCaseEnumConverter(JsonNamingPolicy namingPolicy) + { + _namingPolicy = namingPolicy; + + T[] values = Enum.GetValues(); + string[] names = Enum.GetNames(); + + _readMap = []; + _writeMap = []; + + for (int i = 0; i < names.Length; i++) + { + string converted = namingPolicy.ConvertName(names[i]); + _readMap[converted] = values[i]; + + // Also allow original name for robustness + _readMap.TryAdd(names[i], values[i]); + _writeMap[values[i]] = converted; + } + } + + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + string? value = reader.GetString(); + if (value is not null && _readMap.TryGetValue(value, out T result)) + { + return result; + } + } + + if (reader.TokenType == JsonTokenType.Number && TryReadNumericEnumValue(ref reader, out T numericResult)) + { + return numericResult; + } + + return default; + } + + private static bool TryReadNumericEnumValue(ref Utf8JsonReader reader, out T value) + { + switch (Type.GetTypeCode(Enum.GetUnderlyingType(typeof(T)))) + { + case TypeCode.Byte: + if (reader.TryGetByte(out byte byteValue)) + { + value = (T)Enum.ToObject(typeof(T), byteValue); + return true; + } + + break; + case TypeCode.SByte: + if (reader.TryGetSByte(out sbyte sbyteValue)) + { + value = (T)Enum.ToObject(typeof(T), sbyteValue); + return true; + } + + break; + case TypeCode.Int16: + if (reader.TryGetInt16(out short int16Value)) + { + value = (T)Enum.ToObject(typeof(T), int16Value); + return true; + } + + break; + case TypeCode.UInt16: + if (reader.TryGetUInt16(out ushort uint16Value)) + { + value = (T)Enum.ToObject(typeof(T), uint16Value); + return true; + } + + break; + case TypeCode.Int32: + if (reader.TryGetInt32(out int int32Value)) + { + value = (T)Enum.ToObject(typeof(T), int32Value); + return true; + } + + break; + case TypeCode.UInt32: + if (reader.TryGetUInt32(out uint uint32Value)) + { + value = (T)Enum.ToObject(typeof(T), uint32Value); + return true; + } + + break; + case TypeCode.Int64: + if (reader.TryGetInt64(out long int64Value)) + { + value = (T)Enum.ToObject(typeof(T), int64Value); + return true; + } + + break; + case TypeCode.UInt64: + if (reader.TryGetUInt64(out ulong uint64Value)) + { + value = (T)Enum.ToObject(typeof(T), uint64Value); + return true; + } + + break; + } + + value = default; + return false; + } + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (_writeMap.TryGetValue(value, out string? name)) + { + writer.WriteStringValue(name); + } + else + { + writer.WriteStringValue(_namingPolicy.ConvertName(value.ToString())); + } + } +} diff --git a/src/TorBoxSDK/Http/Json/TorBoxJsonOptions.cs b/src/TorBoxSDK/Http/Json/TorBoxJsonOptions.cs index 49be7e4..7aacf1a 100644 --- a/src/TorBoxSDK/Http/Json/TorBoxJsonOptions.cs +++ b/src/TorBoxSDK/Http/Json/TorBoxJsonOptions.cs @@ -35,7 +35,7 @@ private static JsonSerializerOptions CreateDefault() DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; - options.Converters.Add(new JsonStringEnumConverter(namingPolicy)); + options.Converters.Add(new SnakeCaseEnumConverterFactory(namingPolicy)); options.Converters.Add(new UtcDateTimeOffsetConverter()); options.Converters.Add(new NullableUtcDateTimeOffsetConverter()); diff --git a/src/TorBoxSDK/Http/QueryParameterNameAttribute.cs b/src/TorBoxSDK/Http/QueryParameterNameAttribute.cs new file mode 100644 index 0000000..30b88e3 --- /dev/null +++ b/src/TorBoxSDK/Http/QueryParameterNameAttribute.cs @@ -0,0 +1,20 @@ +namespace TorBoxSDK.Http; + +/// +/// Specifies the query parameter name that a property maps to. +/// This attribute is used on options model properties as metadata +/// for API documentation and discoverability. +/// +/// +/// Initializes a new instance of the class. +/// +/// The query parameter name used in the HTTP query string. +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] +public sealed class QueryParameterNameAttribute(string name) : Attribute +{ + + /// + /// Gets the query parameter name used in the HTTP query string. + /// + public string Name { get; } = name; +} diff --git a/src/TorBoxSDK/Main/Notifications/INotificationsClient.cs b/src/TorBoxSDK/Main/Notifications/INotificationsClient.cs index 42fc6da..a21d047 100644 --- a/src/TorBoxSDK/Main/Notifications/INotificationsClient.cs +++ b/src/TorBoxSDK/Main/Notifications/INotificationsClient.cs @@ -40,18 +40,16 @@ public interface INotificationsClient Task SendTestNotificationAsync(CancellationToken cancellationToken = default); /// Gets the Intercom identity verification hash for the authenticated user. - /// The options containing the auth ID and email. + /// The user's auth identifier. + /// The user's email address. /// Cancellation token. /// The Intercom hash data. /// - /// Thrown when is , or when - /// . or - /// . is . + /// Thrown when or is . /// /// - /// Thrown when . or - /// . is empty. + /// Thrown when or is empty. /// /// Thrown when the API returns an error. - Task> GetIntercomHashAsync(GetIntercomHashOptions options, CancellationToken cancellationToken = default); + Task> GetIntercomHashAsync(string authId, string email, CancellationToken cancellationToken = default); } diff --git a/src/TorBoxSDK/Main/Notifications/NotificationsClient.cs b/src/TorBoxSDK/Main/Notifications/NotificationsClient.cs index 2ce75ff..3a8c5d4 100644 --- a/src/TorBoxSDK/Main/Notifications/NotificationsClient.cs +++ b/src/TorBoxSDK/Main/Notifications/NotificationsClient.cs @@ -124,15 +124,14 @@ public async Task SendTestNotificationAsync(CancellationToken ca } /// - public async Task> GetIntercomHashAsync(GetIntercomHashOptions options, CancellationToken cancellationToken = default) + public async Task> GetIntercomHashAsync(string authId, string email, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNullOrEmpty(options.AuthId, nameof(options.AuthId)); - Guard.ThrowIfNullOrEmpty(options.Email, nameof(options.Email)); + Guard.ThrowIfNullOrEmpty(authId, nameof(authId)); + Guard.ThrowIfNullOrEmpty(email, nameof(email)); string query = TorBoxApiHelper.BuildQuery( - ("auth_id", options.AuthId), - ("email", options.Email)); + ("auth_id", authId), + ("email", email)); using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"intercom/hash{query}"); return await TorBoxApiHelper.SendAsync(_httpClient, httpRequest, cancellationToken).ConfigureAwait(false); diff --git a/src/TorBoxSDK/Main/Stream/IStreamClient.cs b/src/TorBoxSDK/Main/Stream/IStreamClient.cs index 095bb15..2612367 100644 --- a/src/TorBoxSDK/Main/Stream/IStreamClient.cs +++ b/src/TorBoxSDK/Main/Stream/IStreamClient.cs @@ -9,20 +9,25 @@ namespace TorBoxSDK.Main.Stream; public interface IStreamClient { /// Creates a stream for a download item. - /// The stream creation options containing the item ID, file ID, type, and optional track indices. + /// The identifier of the download. + /// The identifier of the file within the download. + /// The type of download (e.g., torrent, usenet). + /// Optional stream creation options for track indices. /// Cancellation token. /// The stream URL or data as a string. - /// Thrown when is , or when the type in is . - /// Thrown when the type in is empty. + /// Thrown when is . + /// Thrown when is empty. /// Thrown when the API returns an error. - Task> CreateStreamAsync(CreateStreamOptions options, CancellationToken cancellationToken = default); + Task> CreateStreamAsync(long id, long fileId, string type, CreateStreamOptions? options = null, CancellationToken cancellationToken = default); /// Gets stream data for a download item. - /// The stream data options containing the presigned token, token, and optional track indices. + /// The presigned token for stream authentication. + /// The authentication token. + /// Optional stream data options for track indices. /// Cancellation token. /// The stream data. - /// Thrown when is , or when the presigned token or token in is . - /// Thrown when the presigned token or token in is empty. + /// Thrown when or is . + /// Thrown when or is empty. /// Thrown when the API returns an error. - Task> GetStreamDataAsync(GetStreamDataOptions options, CancellationToken cancellationToken = default); + Task> GetStreamDataAsync(string presignedToken, string token, GetStreamDataOptions? options = null, CancellationToken cancellationToken = default); } diff --git a/src/TorBoxSDK/Main/Stream/StreamClient.cs b/src/TorBoxSDK/Main/Stream/StreamClient.cs index 338fe86..11ea00c 100644 --- a/src/TorBoxSDK/Main/Stream/StreamClient.cs +++ b/src/TorBoxSDK/Main/Stream/StreamClient.cs @@ -21,36 +21,34 @@ internal sealed class StreamClient(HttpClient httpClient) : IStreamClient private readonly HttpClient _httpClient = Guard.ThrowIfNull(httpClient); /// - public async Task> CreateStreamAsync(CreateStreamOptions options, CancellationToken cancellationToken = default) + public async Task> CreateStreamAsync(long id, long fileId, string type, CreateStreamOptions? options = null, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNullOrEmpty(options.Type, nameof(options.Type)); + Guard.ThrowIfNullOrEmpty(type, nameof(type)); string query = TorBoxApiHelper.BuildQuery( - ("id", options.Id.ToString()), - ("file_id", options.FileId.ToString()), - ("type", options.Type), - ("chosen_subtitle_index", options.ChosenSubtitleIndex?.ToString()), - ("chosen_audio_index", options.ChosenAudioIndex?.ToString()), - ("chosen_resolution_index", options.ChosenResolutionIndex?.ToString())); + ("id", id.ToString()), + ("file_id", fileId.ToString()), + ("type", type), + ("chosen_subtitle_index", options?.ChosenSubtitleIndex?.ToString()), + ("chosen_audio_index", options?.ChosenAudioIndex?.ToString()), + ("chosen_resolution_index", options?.ChosenResolutionIndex?.ToString())); using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"stream/createstream{query}"); return await TorBoxApiHelper.SendAsync(_httpClient, httpRequest, cancellationToken).ConfigureAwait(false); } /// - public async Task> GetStreamDataAsync(GetStreamDataOptions options, CancellationToken cancellationToken = default) + public async Task> GetStreamDataAsync(string presignedToken, string token, GetStreamDataOptions? options = null, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNullOrEmpty(options.PresignedToken, nameof(options.PresignedToken)); - Guard.ThrowIfNullOrEmpty(options.Token, nameof(options.Token)); + Guard.ThrowIfNullOrEmpty(presignedToken, nameof(presignedToken)); + Guard.ThrowIfNullOrEmpty(token, nameof(token)); string query = TorBoxApiHelper.BuildQuery( - ("presigned_token", options.PresignedToken), - ("token", options.Token), - ("chosen_subtitle_index", options.ChosenSubtitleIndex?.ToString()), - ("chosen_audio_index", options.ChosenAudioIndex?.ToString()), - ("chosen_resolution_index", options.ChosenResolutionIndex?.ToString())); + ("presigned_token", presignedToken), + ("token", token), + ("chosen_subtitle_index", options?.ChosenSubtitleIndex?.ToString()), + ("chosen_audio_index", options?.ChosenAudioIndex?.ToString()), + ("chosen_resolution_index", options?.ChosenResolutionIndex?.ToString())); using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"stream/getstreamdata{query}"); return await TorBoxApiHelper.SendAsync(_httpClient, httpRequest, cancellationToken).ConfigureAwait(false); diff --git a/src/TorBoxSDK/Main/Torrents/ITorrentsClient.cs b/src/TorBoxSDK/Main/Torrents/ITorrentsClient.cs index 8af62ab..33a56ba 100644 --- a/src/TorBoxSDK/Main/Torrents/ITorrentsClient.cs +++ b/src/TorBoxSDK/Main/Torrents/ITorrentsClient.cs @@ -25,12 +25,12 @@ public interface ITorrentsClient Task ControlTorrentAsync(ControlTorrentRequest request, CancellationToken cancellationToken = default); /// Requests a download link for a torrent. - /// The download request options. + /// The unique identifier of the torrent to download. + /// Optional download request options. /// Cancellation token. /// The download URL as a string. - /// Thrown when is . /// Thrown when the API returns an error. - Task> RequestDownloadAsync(RequestDownloadOptions options, CancellationToken cancellationToken = default); + Task> RequestDownloadAsync(long torrentId, RequestDownloadOptions? options = null, CancellationToken cancellationToken = default); /// Retrieves the authenticated user's torrent list. /// Optional query parameters for filtering and pagination. @@ -40,12 +40,13 @@ public interface ITorrentsClient Task>> GetMyTorrentListAsync(GetMyListOptions? options = null, CancellationToken cancellationToken = default); /// Checks whether one or more torrent hashes are cached on TorBox (GET). - /// The cache check options containing hashes and optional parameters. + /// The list of hashes to check for cache availability. + /// Optional cache check parameters. /// Cancellation token. /// The cache status data as a dynamic object. - /// Thrown when is , or when the hashes collection in is . + /// Thrown when is . /// Thrown when the API returns an error. - Task> CheckCachedAsync(CheckCachedOptions options, CancellationToken cancellationToken = default); + Task> CheckCachedAsync(IReadOnlyList hashes, CheckCachedOptions? options = null, CancellationToken cancellationToken = default); /// Checks whether one or more torrent hashes are cached on TorBox (POST). /// The cache check request containing hashes and options. @@ -56,21 +57,22 @@ public interface ITorrentsClient Task> CheckCachedByPostAsync(CheckCachedRequest request, CancellationToken cancellationToken = default); /// Exports data for a torrent in the specified format. - /// The export options containing the torrent ID and optional format. + /// The unique identifier of the torrent to export data for. + /// The export type format, or for the default format. /// Cancellation token. /// The exported data as a string. - /// Thrown when is . /// Thrown when the API returns an error. - Task> ExportDataAsync(ExportDataOptions options, CancellationToken cancellationToken = default); + Task> ExportDataAsync(long torrentId, string? exportType = null, CancellationToken cancellationToken = default); /// Retrieves torrent metadata from a hash. - /// The torrent info options containing the hash and optional parameters. + /// The info hash of the torrent. + /// Optional torrent info parameters. /// Cancellation token. /// The torrent metadata information. - /// Thrown when is . - /// Thrown when the hash in is empty. + /// Thrown when is . + /// Thrown when is empty. /// Thrown when the API returns an error. - Task> GetTorrentInfoAsync(GetTorrentInfoOptions options, CancellationToken cancellationToken = default); + Task> GetTorrentInfoAsync(string hash, GetTorrentInfoOptions? options = null, CancellationToken cancellationToken = default); /// Retrieves torrent metadata from a torrent file, magnet URI, or info hash via the POST endpoint. /// The torrent info request containing the file bytes, magnet URI, hash, and options. diff --git a/src/TorBoxSDK/Main/Torrents/TorrentsClient.cs b/src/TorBoxSDK/Main/Torrents/TorrentsClient.cs index 4be5251..91ecd40 100644 --- a/src/TorBoxSDK/Main/Torrents/TorrentsClient.cs +++ b/src/TorBoxSDK/Main/Torrents/TorrentsClient.cs @@ -83,18 +83,16 @@ public async Task ControlTorrentAsync(ControlTorrentRequest requ } /// - public async Task> RequestDownloadAsync(RequestDownloadOptions options, CancellationToken cancellationToken = default) + public async Task> RequestDownloadAsync(long torrentId, RequestDownloadOptions? options = null, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - string query = TorBoxApiHelper.BuildQuery( - ("torrent_id", options.TorrentId.ToString()), - ("file_id", options.FileId?.ToString()), - ("zip_link", options.ZipLink?.ToString().ToLowerInvariant()), - ("token", options.Token), - ("user_ip", options.UserIp), - ("redirect", options.Redirect?.ToString().ToLowerInvariant()), - ("append_name", options.AppendName?.ToString().ToLowerInvariant())); + ("torrent_id", torrentId.ToString()), + ("file_id", options?.FileId?.ToString()), + ("zip_link", options?.ZipLink?.ToString().ToLowerInvariant()), + ("token", options?.Token), + ("user_ip", options?.UserIp), + ("redirect", options?.Redirect?.ToString().ToLowerInvariant()), + ("append_name", options?.AppendName?.ToString().ToLowerInvariant())); using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"torrents/requestdl{query}"); return await TorBoxApiHelper.SendAsync(_httpClient, httpRequest, cancellationToken).ConfigureAwait(false); @@ -114,16 +112,15 @@ public async Task>> GetMyTorrentListAsync( } /// - public async Task> CheckCachedAsync(CheckCachedOptions options, CancellationToken cancellationToken = default) + public async Task> CheckCachedAsync(IReadOnlyList hashes, CheckCachedOptions? options = null, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNull(options.Hashes, $"{nameof(options)}.{nameof(CheckCachedOptions.Hashes)}"); + Guard.ThrowIfNull(hashes, nameof(hashes)); - string hashParam = string.Join(",", options.Hashes); + string hashParam = string.Join(",", hashes); string query = TorBoxApiHelper.BuildQuery( ("hash", hashParam), - ("format", options.Format), - ("list_files", options.ListFiles?.ToString().ToLowerInvariant())); + ("format", options?.Format), + ("list_files", options?.ListFiles?.ToString().ToLowerInvariant())); using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"torrents/checkcached{query}"); return await TorBoxApiHelper.SendAsync(_httpClient, httpRequest, cancellationToken).ConfigureAwait(false); @@ -142,28 +139,25 @@ public async Task> CheckCachedByPostAsync(CheckCachedRequ } /// - public async Task> ExportDataAsync(ExportDataOptions options, CancellationToken cancellationToken = default) + public async Task> ExportDataAsync(long torrentId, string? exportType = null, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - string query = TorBoxApiHelper.BuildQuery( - ("torrent_id", options.TorrentId.ToString()), - ("type", options.ExportType)); + ("torrent_id", torrentId.ToString()), + ("type", exportType)); using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"torrents/exportdata{query}"); return await TorBoxApiHelper.SendAsync(_httpClient, httpRequest, cancellationToken).ConfigureAwait(false); } /// - public async Task> GetTorrentInfoAsync(GetTorrentInfoOptions options, CancellationToken cancellationToken = default) + public async Task> GetTorrentInfoAsync(string hash, GetTorrentInfoOptions? options = null, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNullOrEmpty(options.Hash, nameof(options.Hash)); + Guard.ThrowIfNullOrEmpty(hash, nameof(hash)); string query = TorBoxApiHelper.BuildQuery( - ("hash", options.Hash), - ("timeout", options.Timeout?.ToString()), - ("use_cache_lookup", options.UseCacheLookup?.ToString().ToLowerInvariant())); + ("hash", hash), + ("timeout", options?.Timeout?.ToString()), + ("use_cache_lookup", options?.UseCacheLookup?.ToString().ToLowerInvariant())); using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"torrents/torrentinfo{query}"); return await TorBoxApiHelper.SendAsync(_httpClient, httpRequest, cancellationToken).ConfigureAwait(false); diff --git a/src/TorBoxSDK/Main/Usenet/IUsenetClient.cs b/src/TorBoxSDK/Main/Usenet/IUsenetClient.cs index 36d90a0..768e08a 100644 --- a/src/TorBoxSDK/Main/Usenet/IUsenetClient.cs +++ b/src/TorBoxSDK/Main/Usenet/IUsenetClient.cs @@ -25,12 +25,12 @@ public interface IUsenetClient Task ControlUsenetDownloadAsync(ControlUsenetDownloadRequest request, CancellationToken cancellationToken = default); /// Requests a download link for a Usenet download. - /// The download request options. + /// The unique identifier of the Usenet download. + /// Optional download request options. /// Cancellation token. /// The download URL as a string. - /// Thrown when is . /// Thrown when the API returns an error. - Task> RequestDownloadAsync(RequestUsenetDownloadOptions options, CancellationToken cancellationToken = default); + Task> RequestDownloadAsync(long usenetId, RequestUsenetDownloadOptions? options = null, CancellationToken cancellationToken = default); /// Retrieves the authenticated user's Usenet download list. /// Optional query parameters for filtering and pagination. @@ -40,12 +40,13 @@ public interface IUsenetClient Task>> GetMyUsenetListAsync(GetMyListOptions? options = null, CancellationToken cancellationToken = default); /// Checks whether one or more Usenet hashes are cached on TorBox (GET). - /// The cache check options containing hashes and optional parameters. + /// The list of hashes to check for cache availability. + /// Optional cache check parameters. /// Cancellation token. /// The cache status data as a dynamic object. - /// Thrown when is , or when the hashes collection in is . + /// Thrown when is . /// Thrown when the API returns an error. - Task> CheckCachedAsync(CheckCachedOptions options, CancellationToken cancellationToken = default); + Task> CheckCachedAsync(IReadOnlyList hashes, CheckCachedOptions? options = null, CancellationToken cancellationToken = default); /// Checks whether one or more Usenet hashes are cached on TorBox (POST). /// The cache check request containing hashes and options. diff --git a/src/TorBoxSDK/Main/Usenet/UsenetClient.cs b/src/TorBoxSDK/Main/Usenet/UsenetClient.cs index f2ae021..edab452 100644 --- a/src/TorBoxSDK/Main/Usenet/UsenetClient.cs +++ b/src/TorBoxSDK/Main/Usenet/UsenetClient.cs @@ -83,18 +83,16 @@ public async Task ControlUsenetDownloadAsync(ControlUsenetDownlo } /// - public async Task> RequestDownloadAsync(RequestUsenetDownloadOptions options, CancellationToken cancellationToken = default) + public async Task> RequestDownloadAsync(long usenetId, RequestUsenetDownloadOptions? options = null, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - string query = TorBoxApiHelper.BuildQuery( - ("usenet_id", options.UsenetId.ToString()), - ("file_id", options.FileId?.ToString()), - ("zip_link", options.ZipLink?.ToString().ToLowerInvariant()), - ("token", options.Token), - ("user_ip", options.UserIp), - ("redirect", options.Redirect?.ToString().ToLowerInvariant()), - ("append_name", options.AppendName?.ToString().ToLowerInvariant())); + ("usenet_id", usenetId.ToString()), + ("file_id", options?.FileId?.ToString()), + ("zip_link", options?.ZipLink?.ToString().ToLowerInvariant()), + ("token", options?.Token), + ("user_ip", options?.UserIp), + ("redirect", options?.Redirect?.ToString().ToLowerInvariant()), + ("append_name", options?.AppendName?.ToString().ToLowerInvariant())); using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"usenet/requestdl{query}"); return await TorBoxApiHelper.SendAsync(_httpClient, httpRequest, cancellationToken).ConfigureAwait(false); @@ -114,16 +112,15 @@ public async Task>> GetMyUsenetList } /// - public async Task> CheckCachedAsync(CheckCachedOptions options, CancellationToken cancellationToken = default) + public async Task> CheckCachedAsync(IReadOnlyList hashes, CheckCachedOptions? options = null, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNull(options.Hashes, $"{nameof(options)}.{nameof(CheckCachedOptions.Hashes)}"); + Guard.ThrowIfNull(hashes, nameof(hashes)); - string hashParam = string.Join(",", options.Hashes); + string hashParam = string.Join(",", hashes); string query = TorBoxApiHelper.BuildQuery( ("hash", hashParam), - ("format", options.Format), - ("list_files", options.ListFiles?.ToString().ToLowerInvariant())); + ("format", options?.Format), + ("list_files", options?.ListFiles?.ToString().ToLowerInvariant())); using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"usenet/checkcached{query}"); return await TorBoxApiHelper.SendAsync(_httpClient, httpRequest, cancellationToken).ConfigureAwait(false); diff --git a/src/TorBoxSDK/Main/WebDownloads/IWebDownloadsClient.cs b/src/TorBoxSDK/Main/WebDownloads/IWebDownloadsClient.cs index ff8a809..f47175e 100644 --- a/src/TorBoxSDK/Main/WebDownloads/IWebDownloadsClient.cs +++ b/src/TorBoxSDK/Main/WebDownloads/IWebDownloadsClient.cs @@ -25,12 +25,12 @@ public interface IWebDownloadsClient Task ControlWebDownloadAsync(ControlWebDownloadRequest request, CancellationToken cancellationToken = default); /// Requests a download link for a web download. - /// The download request options. + /// The unique identifier of the web download. + /// Optional download request options. /// Cancellation token. /// The download URL as a string. - /// Thrown when is . /// Thrown when the API returns an error. - Task> RequestDownloadAsync(RequestWebDownloadOptions options, CancellationToken cancellationToken = default); + Task> RequestDownloadAsync(long webId, RequestWebDownloadOptions? options = null, CancellationToken cancellationToken = default); /// Retrieves the authenticated user's web download list. /// Optional query parameters for filtering and pagination. @@ -40,12 +40,13 @@ public interface IWebDownloadsClient Task>> GetMyWebDownloadListAsync(GetMyListOptions? options = null, CancellationToken cancellationToken = default); /// Checks whether one or more web download hashes are cached on TorBox (GET). - /// The cache check options containing hashes and optional parameters. + /// The list of hashes to check for cache availability. + /// Optional cache check parameters. /// Cancellation token. /// The cache status data as a dynamic object. - /// Thrown when is , or when the hashes collection in is . + /// Thrown when is . /// Thrown when the API returns an error. - Task> CheckCachedAsync(CheckCachedOptions options, CancellationToken cancellationToken = default); + Task> CheckCachedAsync(IReadOnlyList hashes, CheckCachedOptions? options = null, CancellationToken cancellationToken = default); /// Checks whether one or more web download hashes are cached on TorBox (POST). /// The cache check request containing hashes and options. diff --git a/src/TorBoxSDK/Main/WebDownloads/WebDownloadsClient.cs b/src/TorBoxSDK/Main/WebDownloads/WebDownloadsClient.cs index 2ec89b3..36f03aa 100644 --- a/src/TorBoxSDK/Main/WebDownloads/WebDownloadsClient.cs +++ b/src/TorBoxSDK/Main/WebDownloads/WebDownloadsClient.cs @@ -45,18 +45,16 @@ public async Task ControlWebDownloadAsync(ControlWebDownloadRequ } /// - public async Task> RequestDownloadAsync(RequestWebDownloadOptions options, CancellationToken cancellationToken = default) + public async Task> RequestDownloadAsync(long webId, RequestWebDownloadOptions? options = null, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - string query = TorBoxApiHelper.BuildQuery( - ("web_id", options.WebId.ToString()), - ("file_id", options.FileId?.ToString()), - ("zip_link", options.ZipLink?.ToString().ToLowerInvariant()), - ("token", options.Token), - ("user_ip", options.UserIp), - ("redirect", options.Redirect?.ToString().ToLowerInvariant()), - ("append_name", options.AppendName?.ToString().ToLowerInvariant())); + ("web_id", webId.ToString()), + ("file_id", options?.FileId?.ToString()), + ("zip_link", options?.ZipLink?.ToString().ToLowerInvariant()), + ("token", options?.Token), + ("user_ip", options?.UserIp), + ("redirect", options?.Redirect?.ToString().ToLowerInvariant()), + ("append_name", options?.AppendName?.ToString().ToLowerInvariant())); using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"webdl/requestdl{query}"); return await TorBoxApiHelper.SendAsync(_httpClient, httpRequest, cancellationToken).ConfigureAwait(false); @@ -76,16 +74,15 @@ public async Task>> GetMyWebDownloadLi } /// - public async Task> CheckCachedAsync(CheckCachedOptions options, CancellationToken cancellationToken = default) + public async Task> CheckCachedAsync(IReadOnlyList hashes, CheckCachedOptions? options = null, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNull(options.Hashes, $"{nameof(options)}.{nameof(CheckCachedOptions.Hashes)}"); + Guard.ThrowIfNull(hashes, nameof(hashes)); - string hashParam = string.Join(",", options.Hashes); + string hashParam = string.Join(",", hashes); string query = TorBoxApiHelper.BuildQuery( ("hash", hashParam), - ("format", options.Format), - ("list_files", options.ListFiles?.ToString().ToLowerInvariant())); + ("format", options?.Format), + ("list_files", options?.ListFiles?.ToString().ToLowerInvariant())); using var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"webdl/checkcached{query}"); return await TorBoxApiHelper.SendAsync(_httpClient, httpRequest, cancellationToken).ConfigureAwait(false); diff --git a/src/TorBoxSDK/Models/Common/CheckCachedOptions.cs b/src/TorBoxSDK/Models/Common/CheckCachedOptions.cs index b5d5a57..c777c2f 100644 --- a/src/TorBoxSDK/Models/Common/CheckCachedOptions.cs +++ b/src/TorBoxSDK/Models/Common/CheckCachedOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.Common; @@ -11,23 +11,17 @@ namespace TorBoxSDK.Models.Common; /// public sealed record CheckCachedOptions { - /// - /// Gets the list of hashes to check for cache availability. - /// - [JsonPropertyName("hash")] - public IReadOnlyList Hashes { get; init; } = []; - /// /// Gets the optional response format, /// or to use the default format. /// - [JsonPropertyName("format")] + [QueryParameterName("format")] public string? Format { get; init; } /// /// Gets a value indicating whether to include file listings in the response, /// or to use the default behavior. /// - [JsonPropertyName("list_files")] + [QueryParameterName("list_files")] public bool? ListFiles { get; init; } } diff --git a/src/TorBoxSDK/Models/Common/DownloadStatus.cs b/src/TorBoxSDK/Models/Common/DownloadStatus.cs index 424c259..d07cf86 100644 --- a/src/TorBoxSDK/Models/Common/DownloadStatus.cs +++ b/src/TorBoxSDK/Models/Common/DownloadStatus.cs @@ -1,11 +1,8 @@ -using System.Text.Json.Serialization; - namespace TorBoxSDK.Models.Common; /// /// Enumerates the possible states of a download item. /// -[JsonConverter(typeof(JsonStringEnumConverter))] public enum DownloadStatus { /// An unknown or unmapped download status was returned. diff --git a/src/TorBoxSDK/Models/Common/GetMyListOptions.cs b/src/TorBoxSDK/Models/Common/GetMyListOptions.cs index 86e220e..5ca7ff2 100644 --- a/src/TorBoxSDK/Models/Common/GetMyListOptions.cs +++ b/src/TorBoxSDK/Models/Common/GetMyListOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.Common; @@ -14,27 +14,27 @@ public sealed record GetMyListOptions /// Gets the optional item ID to retrieve a single item, /// or to retrieve the full list. /// - [JsonPropertyName("id")] + [QueryParameterName("id")] public long? Id { get; init; } /// /// Gets the optional offset for pagination, /// or to use the server default. /// - [JsonPropertyName("offset")] + [QueryParameterName("offset")] public int? Offset { get; init; } /// /// Gets the optional limit for pagination, /// or to use the server default. /// - [JsonPropertyName("limit")] + [QueryParameterName("limit")] public int? Limit { get; init; } /// /// Gets a value indicating whether to bypass the cache, /// or to use the default caching behavior. /// - [JsonPropertyName("bypass_cache")] + [QueryParameterName("bypass_cache")] public bool? BypassCache { get; init; } } diff --git a/src/TorBoxSDK/Models/General/Changelog.cs b/src/TorBoxSDK/Models/General/Changelog.cs index 0581ec1..129e155 100644 --- a/src/TorBoxSDK/Models/General/Changelog.cs +++ b/src/TorBoxSDK/Models/General/Changelog.cs @@ -12,7 +12,7 @@ public sealed record Changelog /// Gets the unique identifier of the changelog entry. /// [JsonPropertyName("id")] - public int Id { get; init; } + public string Id { get; init; } = string.Empty; /// /// Gets the version name of the changelog entry. diff --git a/src/TorBoxSDK/Models/General/SpeedtestOptions.cs b/src/TorBoxSDK/Models/General/SpeedtestOptions.cs index e99b20c..cb7d60a 100644 --- a/src/TorBoxSDK/Models/General/SpeedtestOptions.cs +++ b/src/TorBoxSDK/Models/General/SpeedtestOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.General; @@ -11,20 +11,20 @@ public sealed record SpeedtestOptions /// Gets the user's IP address for the speedtest, /// or to use the caller's IP. /// - [JsonPropertyName("user_ip")] + [QueryParameterName("user_ip")] public string? UserIp { get; init; } /// /// Gets the region for the speedtest, /// or to auto-detect. /// - [JsonPropertyName("region")] + [QueryParameterName("region")] public string? Region { get; init; } /// /// Gets the test length value (e.g. "short", "long"), /// or to use the default. /// - [JsonPropertyName("test_length")] + [QueryParameterName("test_length")] public string? TestLength { get; init; } } diff --git a/src/TorBoxSDK/Models/Notifications/GetIntercomHashOptions.cs b/src/TorBoxSDK/Models/Notifications/GetIntercomHashOptions.cs deleted file mode 100644 index fe8aeb1..0000000 --- a/src/TorBoxSDK/Models/Notifications/GetIntercomHashOptions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TorBoxSDK.Models.Notifications; - -/// -/// Represents the query parameters for getting the Intercom identity verification hash. -/// -/// -/// These options are sent as query string parameters, not as a JSON body. -/// -public sealed record GetIntercomHashOptions -{ - /// - /// Gets the user's auth identifier. - /// - [JsonPropertyName("auth_id")] - public string AuthId { get; init; } = string.Empty; - - /// - /// Gets the user's email address. - /// - [JsonPropertyName("email")] - public string Email { get; init; } = string.Empty; -} diff --git a/src/TorBoxSDK/Models/Queued/GetQueuedOptions.cs b/src/TorBoxSDK/Models/Queued/GetQueuedOptions.cs index c7760b3..f9966d8 100644 --- a/src/TorBoxSDK/Models/Queued/GetQueuedOptions.cs +++ b/src/TorBoxSDK/Models/Queued/GetQueuedOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.Queued; @@ -14,34 +14,34 @@ public sealed record GetQueuedOptions /// Gets the optional queued download ID to retrieve a single item, /// or to retrieve the full list. /// - [JsonPropertyName("id")] + [QueryParameterName("id")] public long? Id { get; init; } /// /// Gets the optional offset for pagination, /// or to use the server default. /// - [JsonPropertyName("offset")] + [QueryParameterName("offset")] public int? Offset { get; init; } /// /// Gets the optional limit for pagination, /// or to use the server default. /// - [JsonPropertyName("limit")] + [QueryParameterName("limit")] public int? Limit { get; init; } /// /// Gets a value indicating whether to bypass the cache, /// or to use the default caching behavior. /// - [JsonPropertyName("bypass_cache")] + [QueryParameterName("bypass_cache")] public bool? BypassCache { get; init; } /// /// Gets the optional type filter for queued downloads, /// or to include all types. /// - [JsonPropertyName("type")] + [QueryParameterName("type")] public string? Type { get; init; } } diff --git a/src/TorBoxSDK/Models/Relay/CheckInactiveOptions.cs b/src/TorBoxSDK/Models/Relay/CheckInactiveOptions.cs deleted file mode 100644 index 6cb09eb..0000000 --- a/src/TorBoxSDK/Models/Relay/CheckInactiveOptions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TorBoxSDK.Models.Relay; - -/// -/// Represents the parameters for checking whether a torrent is inactive on the relay. -/// -/// -/// These values are used as URL path segments for the inactive check endpoint. -/// -public sealed record CheckInactiveOptions -{ - /// - /// Gets the authentication identifier of the user. - /// - [JsonPropertyName("auth_id")] - public string AuthId { get; init; } = string.Empty; - - /// - /// Gets the unique identifier of the torrent to check. - /// - [JsonPropertyName("torrent_id")] - public long TorrentId { get; init; } -} diff --git a/src/TorBoxSDK/Models/Search/DownloadUsenetOptions.cs b/src/TorBoxSDK/Models/Search/DownloadUsenetOptions.cs deleted file mode 100644 index 2daa36a..0000000 --- a/src/TorBoxSDK/Models/Search/DownloadUsenetOptions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TorBoxSDK.Models.Search; - -/// -/// Represents the parameters for downloading a usenet NZB file. -/// -/// -/// These values are used as URL path segments for the download endpoint. -/// -public sealed record DownloadUsenetOptions -{ - /// - /// Gets the unique identifier of the usenet article. - /// - [JsonPropertyName("id")] - public string Id { get; init; } = string.Empty; - - /// - /// Gets the GUID of the NZB file to download. - /// - [JsonPropertyName("guid")] - public string Guid { get; init; } = string.Empty; -} diff --git a/src/TorBoxSDK/Models/Search/MetaSearchOptions.cs b/src/TorBoxSDK/Models/Search/MetaSearchOptions.cs index 536bbae..9a8d41b 100644 --- a/src/TorBoxSDK/Models/Search/MetaSearchOptions.cs +++ b/src/TorBoxSDK/Models/Search/MetaSearchOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.Search; @@ -11,6 +11,6 @@ public sealed record MetaSearchOptions /// Gets the content type to filter metadata results by, /// or to include all content types. /// - [JsonPropertyName("type")] + [QueryParameterName("type")] public string? Type { get; init; } } diff --git a/src/TorBoxSDK/Models/Search/SearchNewznabOptions.cs b/src/TorBoxSDK/Models/Search/SearchNewznabOptions.cs deleted file mode 100644 index fcd9454..0000000 --- a/src/TorBoxSDK/Models/Search/SearchNewznabOptions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TorBoxSDK.Models.Search; - -/// -/// Represents the query parameters for searching the Newznab API. -/// -/// -/// These options are sent as query string parameters, not as a JSON body. -/// -public sealed record SearchNewznabOptions -{ - /// - /// Gets the search query string. - /// - [JsonPropertyName("q")] - public string Query { get; init; } = string.Empty; - - /// - /// Gets the optional API key override for the Newznab endpoint, - /// or to use the default key. - /// - [JsonPropertyName("apikey")] - public string? ApiKey { get; init; } -} diff --git a/src/TorBoxSDK/Models/Search/SearchTorznabOptions.cs b/src/TorBoxSDK/Models/Search/SearchTorznabOptions.cs deleted file mode 100644 index 780afa8..0000000 --- a/src/TorBoxSDK/Models/Search/SearchTorznabOptions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TorBoxSDK.Models.Search; - -/// -/// Represents the query parameters for searching the Torznab API. -/// -/// -/// These options are sent as query string parameters, not as a JSON body. -/// -public sealed record SearchTorznabOptions -{ - /// - /// Gets the search query string. - /// - [JsonPropertyName("q")] - public string Query { get; init; } = string.Empty; - - /// - /// Gets the optional API key override for the Torznab endpoint, - /// or to use the default key. - /// - [JsonPropertyName("apikey")] - public string? ApiKey { get; init; } -} diff --git a/src/TorBoxSDK/Models/Search/TorrentSearchOptions.cs b/src/TorBoxSDK/Models/Search/TorrentSearchOptions.cs index 6458375..69ec0cf 100644 --- a/src/TorBoxSDK/Models/Search/TorrentSearchOptions.cs +++ b/src/TorBoxSDK/Models/Search/TorrentSearchOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.Search; @@ -11,48 +11,48 @@ public sealed record TorrentSearchOptions /// Gets a value indicating whether to include metadata in the search results, /// or to use the server default. /// - [JsonPropertyName("metadata")] + [QueryParameterName("metadata")] public bool? Metadata { get; init; } /// /// Gets the season number to filter results by, /// or to include all seasons. /// - [JsonPropertyName("season")] + [QueryParameterName("season")] public int? Season { get; init; } /// /// Gets the episode number to filter results by, /// or to include all episodes. /// - [JsonPropertyName("episode")] + [QueryParameterName("episode")] public int? Episode { get; init; } /// /// Gets a value indicating whether to check if the results are cached, /// or to use the server default. /// - [JsonPropertyName("check_cache")] + [QueryParameterName("check_cache")] public bool? CheckCache { get; init; } /// /// Gets a value indicating whether to check if the results are already owned, /// or to use the server default. /// - [JsonPropertyName("check_owned")] + [QueryParameterName("check_owned")] public bool? CheckOwned { get; init; } /// /// Gets a value indicating whether to use custom user search engines, /// or to use the server default. /// - [JsonPropertyName("search_user_engines")] + [QueryParameterName("search_user_engines")] public bool? SearchUserEngines { get; init; } /// /// Gets a value indicating whether to only return cached results, /// or to use the server default. /// - [JsonPropertyName("cached_only")] + [QueryParameterName("cached_only")] public bool? CachedOnly { get; init; } } diff --git a/src/TorBoxSDK/Models/Search/UsenetSearchOptions.cs b/src/TorBoxSDK/Models/Search/UsenetSearchOptions.cs index df345ca..a6b51c4 100644 --- a/src/TorBoxSDK/Models/Search/UsenetSearchOptions.cs +++ b/src/TorBoxSDK/Models/Search/UsenetSearchOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.Search; @@ -11,48 +11,48 @@ public sealed record UsenetSearchOptions /// Gets a value indicating whether to include metadata in the search results, /// or to use the server default. /// - [JsonPropertyName("metadata")] + [QueryParameterName("metadata")] public bool? Metadata { get; init; } /// /// Gets the season number to filter results by, /// or to include all seasons. /// - [JsonPropertyName("season")] + [QueryParameterName("season")] public int? Season { get; init; } /// /// Gets the episode number to filter results by, /// or to include all episodes. /// - [JsonPropertyName("episode")] + [QueryParameterName("episode")] public int? Episode { get; init; } /// /// Gets a value indicating whether to check if the results are cached, /// or to use the server default. /// - [JsonPropertyName("check_cache")] + [QueryParameterName("check_cache")] public bool? CheckCache { get; init; } /// /// Gets a value indicating whether to check if the results are already owned, /// or to use the server default. /// - [JsonPropertyName("check_owned")] + [QueryParameterName("check_owned")] public bool? CheckOwned { get; init; } /// /// Gets a value indicating whether to use custom user search engines, /// or to use the server default. /// - [JsonPropertyName("search_user_engines")] + [QueryParameterName("search_user_engines")] public bool? SearchUserEngines { get; init; } /// /// Gets a value indicating whether to only return cached results, /// or to use the server default. /// - [JsonPropertyName("cached_only")] + [QueryParameterName("cached_only")] public bool? CachedOnly { get; init; } } diff --git a/src/TorBoxSDK/Models/Stream/CreateStreamOptions.cs b/src/TorBoxSDK/Models/Stream/CreateStreamOptions.cs index 804a3a9..cf50239 100644 --- a/src/TorBoxSDK/Models/Stream/CreateStreamOptions.cs +++ b/src/TorBoxSDK/Models/Stream/CreateStreamOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.Stream; @@ -10,42 +10,24 @@ namespace TorBoxSDK.Models.Stream; /// public sealed record CreateStreamOptions { - /// - /// Gets the identifier of the download. - /// - [JsonPropertyName("id")] - public long Id { get; init; } - - /// - /// Gets the identifier of the file within the download. - /// - [JsonPropertyName("file_id")] - public long FileId { get; init; } - - /// - /// Gets the type of download (e.g., torrent, usenet). - /// - [JsonPropertyName("type")] - public string Type { get; init; } = string.Empty; - /// /// Gets the optional index of the chosen subtitle track, /// or to use the default. /// - [JsonPropertyName("chosen_subtitle_index")] + [QueryParameterName("chosen_subtitle_index")] public int? ChosenSubtitleIndex { get; init; } /// /// Gets the optional index of the chosen audio track, /// or to use the default. /// - [JsonPropertyName("chosen_audio_index")] + [QueryParameterName("chosen_audio_index")] public int? ChosenAudioIndex { get; init; } /// /// Gets the optional index of the chosen resolution, /// or to use the default. /// - [JsonPropertyName("chosen_resolution_index")] + [QueryParameterName("chosen_resolution_index")] public int? ChosenResolutionIndex { get; init; } } diff --git a/src/TorBoxSDK/Models/Stream/GetStreamDataOptions.cs b/src/TorBoxSDK/Models/Stream/GetStreamDataOptions.cs index be7f59b..aa0b4a5 100644 --- a/src/TorBoxSDK/Models/Stream/GetStreamDataOptions.cs +++ b/src/TorBoxSDK/Models/Stream/GetStreamDataOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.Stream; @@ -10,36 +10,24 @@ namespace TorBoxSDK.Models.Stream; /// public sealed record GetStreamDataOptions { - /// - /// Gets the presigned token for stream authentication. - /// - [JsonPropertyName("presigned_token")] - public string PresignedToken { get; init; } = string.Empty; - - /// - /// Gets the authentication token. - /// - [JsonPropertyName("token")] - public string Token { get; init; } = string.Empty; - /// /// Gets the optional index of the chosen subtitle track, /// or to use the default. /// - [JsonPropertyName("chosen_subtitle_index")] + [QueryParameterName("chosen_subtitle_index")] public int? ChosenSubtitleIndex { get; init; } /// /// Gets the optional index of the chosen audio track, /// or to use the default. /// - [JsonPropertyName("chosen_audio_index")] + [QueryParameterName("chosen_audio_index")] public int? ChosenAudioIndex { get; init; } /// /// Gets the optional index of the chosen resolution, /// or to use the default. /// - [JsonPropertyName("chosen_resolution_index")] + [QueryParameterName("chosen_resolution_index")] public int? ChosenResolutionIndex { get; init; } } diff --git a/src/TorBoxSDK/Models/Torrents/ExportDataOptions.cs b/src/TorBoxSDK/Models/Torrents/ExportDataOptions.cs deleted file mode 100644 index c6a0c8a..0000000 --- a/src/TorBoxSDK/Models/Torrents/ExportDataOptions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json.Serialization; - -namespace TorBoxSDK.Models.Torrents; - -/// -/// Represents the options for exporting torrent data. -/// -public sealed record ExportDataOptions -{ - /// - /// Gets the unique identifier of the torrent to export data for. - /// - [JsonPropertyName("torrent_id")] - public long TorrentId { get; init; } - - /// - /// Gets the export type format, or for the default format. - /// - [JsonPropertyName("type")] - public string? ExportType { get; init; } -} diff --git a/src/TorBoxSDK/Models/Torrents/GetTorrentInfoOptions.cs b/src/TorBoxSDK/Models/Torrents/GetTorrentInfoOptions.cs index a3f07b4..cedebca 100644 --- a/src/TorBoxSDK/Models/Torrents/GetTorrentInfoOptions.cs +++ b/src/TorBoxSDK/Models/Torrents/GetTorrentInfoOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.Torrents; @@ -10,23 +10,17 @@ namespace TorBoxSDK.Models.Torrents; /// public sealed record GetTorrentInfoOptions { - /// - /// Gets the info hash of the torrent. - /// - [JsonPropertyName("hash")] - public string Hash { get; init; } = string.Empty; - /// /// Gets the optional timeout in seconds for metadata retrieval, /// or to use the server default. /// - [JsonPropertyName("timeout")] + [QueryParameterName("timeout")] public int? Timeout { get; init; } /// /// Gets a value indicating whether to enable cache lookup for the torrent info, /// or to use the default behavior. /// - [JsonPropertyName("use_cache_lookup")] + [QueryParameterName("use_cache_lookup")] public bool? UseCacheLookup { get; init; } } diff --git a/src/TorBoxSDK/Models/Torrents/RequestDownloadOptions.cs b/src/TorBoxSDK/Models/Torrents/RequestDownloadOptions.cs index d9258e8..19c5f66 100644 --- a/src/TorBoxSDK/Models/Torrents/RequestDownloadOptions.cs +++ b/src/TorBoxSDK/Models/Torrents/RequestDownloadOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.Torrents; @@ -7,56 +7,50 @@ namespace TorBoxSDK.Models.Torrents; /// /// /// These options are sent as query string parameters, not as a JSON body. -/// The attributes are provided for consistent -/// naming conventions with the API. +/// The attributes document the +/// corresponding API query parameter names. /// public sealed record RequestDownloadOptions { - /// - /// Gets the unique identifier of the torrent to download. - /// - [JsonPropertyName("torrent_id")] - public long TorrentId { get; init; } - /// /// Gets the identifier of a specific file within the torrent to download, /// or to download all files. /// - [JsonPropertyName("file_id")] + [QueryParameterName("file_id")] public long? FileId { get; init; } /// /// Gets a value indicating whether to return a zip download link, /// or to use the default behavior. /// - [JsonPropertyName("zip_link")] + [QueryParameterName("zip_link")] public bool? ZipLink { get; init; } /// /// Gets the IP address of the user requesting the download, /// or to omit. /// - [JsonPropertyName("user_ip")] + [QueryParameterName("user_ip")] public string? UserIp { get; init; } /// /// Gets a value indicating whether to redirect to the download URL, /// or to return the URL in the response body. /// - [JsonPropertyName("redirect")] + [QueryParameterName("redirect")] public bool? Redirect { get; init; } /// /// Gets the API token to use for authentication, /// or to use the client's default token. /// - [JsonPropertyName("token")] + [QueryParameterName("token")] public string? Token { get; init; } /// /// Gets a value indicating whether to append the file name to the download URL, /// or to use the default behavior. /// - [JsonPropertyName("append_name")] + [QueryParameterName("append_name")] public bool? AppendName { get; init; } } diff --git a/src/TorBoxSDK/Models/Usenet/RequestUsenetDownloadOptions.cs b/src/TorBoxSDK/Models/Usenet/RequestUsenetDownloadOptions.cs index d277e99..9fb0242 100644 --- a/src/TorBoxSDK/Models/Usenet/RequestUsenetDownloadOptions.cs +++ b/src/TorBoxSDK/Models/Usenet/RequestUsenetDownloadOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.Usenet; @@ -10,51 +10,45 @@ namespace TorBoxSDK.Models.Usenet; /// public sealed record RequestUsenetDownloadOptions { - /// - /// Gets the unique identifier of the Usenet download. - /// - [JsonPropertyName("usenet_id")] - public long UsenetId { get; init; } - /// /// Gets the identifier of a specific file within the download, /// or to download all files. /// - [JsonPropertyName("file_id")] + [QueryParameterName("file_id")] public long? FileId { get; init; } /// /// Gets a value indicating whether to return a zip download link, /// or to use the default behavior. /// - [JsonPropertyName("zip_link")] + [QueryParameterName("zip_link")] public bool? ZipLink { get; init; } /// /// Gets the IP address of the user requesting the download, /// or to omit. /// - [JsonPropertyName("user_ip")] + [QueryParameterName("user_ip")] public string? UserIp { get; init; } /// /// Gets the API token to use for authentication, /// or to use the client's default token. /// - [JsonPropertyName("token")] + [QueryParameterName("token")] public string? Token { get; init; } /// /// Gets a value indicating whether to redirect to the download URL, /// or to return the URL in the response body. /// - [JsonPropertyName("redirect")] + [QueryParameterName("redirect")] public bool? Redirect { get; init; } /// /// Gets a value indicating whether to append the file name to the download URL, /// or to use the default behavior. /// - [JsonPropertyName("append_name")] + [QueryParameterName("append_name")] public bool? AppendName { get; init; } } diff --git a/src/TorBoxSDK/Models/WebDownloads/RequestWebDownloadOptions.cs b/src/TorBoxSDK/Models/WebDownloads/RequestWebDownloadOptions.cs index 03ed056..0c73ef9 100644 --- a/src/TorBoxSDK/Models/WebDownloads/RequestWebDownloadOptions.cs +++ b/src/TorBoxSDK/Models/WebDownloads/RequestWebDownloadOptions.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using TorBoxSDK.Http; namespace TorBoxSDK.Models.WebDownloads; @@ -10,51 +10,45 @@ namespace TorBoxSDK.Models.WebDownloads; /// public sealed record RequestWebDownloadOptions { - /// - /// Gets the unique identifier of the web download. - /// - [JsonPropertyName("web_id")] - public long WebId { get; init; } - /// /// Gets the identifier of a specific file within the download, /// or to download all files. /// - [JsonPropertyName("file_id")] + [QueryParameterName("file_id")] public long? FileId { get; init; } /// /// Gets a value indicating whether to return a zip download link, /// or to use the default behavior. /// - [JsonPropertyName("zip_link")] + [QueryParameterName("zip_link")] public bool? ZipLink { get; init; } /// /// Gets the IP address of the user requesting the download, /// or to omit. /// - [JsonPropertyName("user_ip")] + [QueryParameterName("user_ip")] public string? UserIp { get; init; } /// /// Gets the API token to use for authentication, /// or to use the client's default token. /// - [JsonPropertyName("token")] + [QueryParameterName("token")] public string? Token { get; init; } /// /// Gets a value indicating whether to redirect to the download URL, /// or to return the URL in the response body. /// - [JsonPropertyName("redirect")] + [QueryParameterName("redirect")] public bool? Redirect { get; init; } /// /// Gets a value indicating whether to append the file name to the download URL, /// or to use the default behavior. /// - [JsonPropertyName("append_name")] + [QueryParameterName("append_name")] public bool? AppendName { get; init; } } diff --git a/src/TorBoxSDK/Relay/IRelayApiClient.cs b/src/TorBoxSDK/Relay/IRelayApiClient.cs index a3f348b..1ebbc82 100644 --- a/src/TorBoxSDK/Relay/IRelayApiClient.cs +++ b/src/TorBoxSDK/Relay/IRelayApiClient.cs @@ -15,11 +15,12 @@ public interface IRelayApiClient Task> GetStatusAsync(CancellationToken cancellationToken = default); /// Checks whether a torrent is inactive on the relay. - /// The options containing the auth ID and torrent ID. + /// The authentication identifier of the user. + /// The unique identifier of the torrent to check. /// Cancellation token. /// The inactivity check result. - /// Thrown when is , or when the auth ID in is . - /// Thrown when the auth ID in is empty. + /// Thrown when is . + /// Thrown when is empty. /// Thrown when the API returns an error. - Task> CheckForInactiveAsync(CheckInactiveOptions options, CancellationToken cancellationToken = default); + Task> CheckForInactiveAsync(string authId, long torrentId, CancellationToken cancellationToken = default); } diff --git a/src/TorBoxSDK/Relay/RelayApiClient.cs b/src/TorBoxSDK/Relay/RelayApiClient.cs index 96ddd1e..d090a53 100644 --- a/src/TorBoxSDK/Relay/RelayApiClient.cs +++ b/src/TorBoxSDK/Relay/RelayApiClient.cs @@ -55,10 +55,7 @@ public async Task> GetStatusAsync(CancellationToken if (!httpResponse.IsSuccessStatusCode) { - throw new TorBoxException( - $"HTTP {(int)httpResponse.StatusCode}: {httpResponse.ReasonPhrase}", - TorBoxErrorCode.ServerError, - content); + throw new TorBoxException($"HTTP {(int)httpResponse.StatusCode}: {httpResponse.ReasonPhrase}", TorBoxErrorCode.ServerError, content); } // The relay status endpoint returns a non-standard response @@ -74,12 +71,11 @@ public async Task> GetStatusAsync(CancellationToken } /// - public async Task> CheckForInactiveAsync(CheckInactiveOptions options, CancellationToken cancellationToken = default) + public async Task> CheckForInactiveAsync(string authId, long torrentId, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNullOrEmpty(options.AuthId, nameof(options.AuthId)); + Guard.ThrowIfNullOrEmpty(authId, nameof(authId)); - using var request = new HttpRequestMessage(HttpMethod.Get, $"inactivecheck/torrent/{Uri.EscapeDataString(options.AuthId)}/{options.TorrentId}"); + using var request = new HttpRequestMessage(HttpMethod.Get, $"inactivecheck/torrent/{Uri.EscapeDataString(authId)}/{torrentId}"); return await TorBoxApiHelper.SendAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); } } diff --git a/src/TorBoxSDK/Search/ISearchApiClient.cs b/src/TorBoxSDK/Search/ISearchApiClient.cs index e78a342..4968cd0 100644 --- a/src/TorBoxSDK/Search/ISearchApiClient.cs +++ b/src/TorBoxSDK/Search/ISearchApiClient.cs @@ -62,13 +62,14 @@ public interface ISearchApiClient Task> GetUsenetByIdAsync(string id, UsenetSearchOptions? options = null, CancellationToken cancellationToken = default); /// Downloads an NZB file for a usenet article. - /// The download options containing the article ID and GUID. + /// The unique identifier of the usenet article. + /// The GUID of the NZB file to download. /// Cancellation token. /// The NZB download data as a string. - /// Thrown when is , or when the ID or GUID in is . - /// Thrown when the ID or GUID in is empty. + /// Thrown when or is . + /// Thrown when or is empty. /// Thrown when the API returns an error. - Task> DownloadUsenetAsync(DownloadUsenetOptions options, CancellationToken cancellationToken = default); + Task> DownloadUsenetAsync(string id, string guid, CancellationToken cancellationToken = default); /// Retrieves the meta search tutorial and information page. /// Cancellation token. @@ -96,20 +97,22 @@ public interface ISearchApiClient Task> GetMetaByIdAsync(string id, CancellationToken cancellationToken = default); /// Searches the Torznab API for torrents matching the specified query. - /// The search options containing the query and optional API key. + /// The search query string. + /// Optional API key override for the Torznab endpoint, or to use the default key. /// Cancellation token. /// The search results as an XML string. - /// Thrown when is , or when the query in is . - /// Thrown when the query in is empty. + /// Thrown when is . + /// Thrown when is empty. /// Thrown when the API returns an error. - Task> SearchTorznabAsync(SearchTorznabOptions options, CancellationToken cancellationToken = default); + Task> SearchTorznabAsync(string query, string? apiKey = null, CancellationToken cancellationToken = default); /// Searches the Newznab API for usenet articles matching the specified query. - /// The search options containing the query and optional API key. + /// The search query string. + /// Optional API key override for the Newznab endpoint, or to use the default key. /// Cancellation token. /// The search results as an XML string. - /// Thrown when is , or when the query in is . - /// Thrown when the query in is empty. + /// Thrown when is . + /// Thrown when is empty. /// Thrown when the API returns an error. - Task> SearchNewznabAsync(SearchNewznabOptions options, CancellationToken cancellationToken = default); + Task> SearchNewznabAsync(string query, string? apiKey = null, CancellationToken cancellationToken = default); } diff --git a/src/TorBoxSDK/Search/SearchApiClient.cs b/src/TorBoxSDK/Search/SearchApiClient.cs index 81f700f..7382337 100644 --- a/src/TorBoxSDK/Search/SearchApiClient.cs +++ b/src/TorBoxSDK/Search/SearchApiClient.cs @@ -75,13 +75,12 @@ public async Task> GetUsenetByIdAsync(string } /// - public async Task> DownloadUsenetAsync(DownloadUsenetOptions options, CancellationToken cancellationToken = default) + public async Task> DownloadUsenetAsync(string id, string guid, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNullOrEmpty(options.Id, nameof(options.Id)); - Guard.ThrowIfNullOrEmpty(options.Guid, nameof(options.Guid)); + Guard.ThrowIfNullOrEmpty(id, nameof(id)); + Guard.ThrowIfNullOrEmpty(guid, nameof(guid)); - using var request = new HttpRequestMessage(HttpMethod.Get, $"usenet/download/{Uri.EscapeDataString(options.Id)}/{Uri.EscapeDataString(options.Guid)}"); + using var request = new HttpRequestMessage(HttpMethod.Get, $"usenet/download/{Uri.EscapeDataString(id)}/{Uri.EscapeDataString(guid)}"); return await TorBoxApiHelper.SendAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); } @@ -113,30 +112,28 @@ public async Task> GetMetaByIdAsync(string id, } /// - public async Task> SearchTorznabAsync(SearchTorznabOptions options, CancellationToken cancellationToken = default) + public async Task> SearchTorznabAsync(string query, string? apiKey = null, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNullOrEmpty(options.Query, nameof(options.Query)); + Guard.ThrowIfNullOrEmpty(query, nameof(query)); string queryString = TorBoxApiHelper.BuildQuery( ("t", "search"), - ("q", options.Query), - ("apikey", options.ApiKey)); + ("q", query), + ("apikey", apiKey)); using var request = new HttpRequestMessage(HttpMethod.Get, $"torznab/api{queryString}"); return await TorBoxApiHelper.SendAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); } /// - public async Task> SearchNewznabAsync(SearchNewznabOptions options, CancellationToken cancellationToken = default) + public async Task> SearchNewznabAsync(string query, string? apiKey = null, CancellationToken cancellationToken = default) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNullOrEmpty(options.Query, nameof(options.Query)); + Guard.ThrowIfNullOrEmpty(query, nameof(query)); string queryString = TorBoxApiHelper.BuildQuery( ("t", "search"), - ("q", options.Query), - ("apikey", options.ApiKey)); + ("q", query), + ("apikey", apiKey)); using var request = new HttpRequestMessage(HttpMethod.Get, $"newznab/api{queryString}"); return await TorBoxApiHelper.SendAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); diff --git a/src/TorBoxSDK/TorBoxClient.cs b/src/TorBoxSDK/TorBoxClient.cs index 3c37c1e..09b6417 100644 --- a/src/TorBoxSDK/TorBoxClient.cs +++ b/src/TorBoxSDK/TorBoxClient.cs @@ -28,6 +28,15 @@ namespace TorBoxSDK; /// public sealed class TorBoxClient : ITorBoxClient { + /// + public IMainApiClient Main { get; } + + /// + public ISearchApiClient Search { get; } + + /// + public IRelayApiClient Relay { get; } + /// /// Initializes a new instance of the class /// using an to create the required HTTP clients. @@ -50,13 +59,4 @@ internal TorBoxClient(IHttpClientFactory httpClientFactory, IOptions - public IMainApiClient Main { get; } - - /// - public ISearchApiClient Search { get; } - - /// - public IRelayApiClient Relay { get; } } diff --git a/tests/TorBoxSDK.IntegrationTests/Main/General/GeneralClientIntegrationTests.cs b/tests/TorBoxSDK.IntegrationTests/Main/General/GeneralClientIntegrationTests.cs index bd72143..6c316dd 100644 --- a/tests/TorBoxSDK.IntegrationTests/Main/General/GeneralClientIntegrationTests.cs +++ b/tests/TorBoxSDK.IntegrationTests/Main/General/GeneralClientIntegrationTests.cs @@ -1,4 +1,4 @@ -using TorBoxSDK.IntegrationTests.Helpers; +using TorBoxSDK.IntegrationTests.Helpers; using TorBoxSDK.Models.Common; using TorBoxSDK.Models.General; @@ -106,8 +106,7 @@ public async Task GetChangelogsJsonAsync_WithValidApiKey_ReturnsChangelogs() using CancellationTokenSource cts = new(TimeSpan.FromMinutes(1)); // Act - TorBoxResponse> response = await _fixture.Client.Main.General - .GetChangelogsJsonAsync(cts.Token); + TorBoxResponse> response = await _fixture.Client.Main.General.GetChangelogsJsonAsync(cts.Token); // Assert Assert.NotNull(response); diff --git a/tests/TorBoxSDK.IntegrationTests/Main/Notifications/NotificationsClientIntegrationTests.cs b/tests/TorBoxSDK.IntegrationTests/Main/Notifications/NotificationsClientIntegrationTests.cs index bb2f351..9d197eb 100644 --- a/tests/TorBoxSDK.IntegrationTests/Main/Notifications/NotificationsClientIntegrationTests.cs +++ b/tests/TorBoxSDK.IntegrationTests/Main/Notifications/NotificationsClientIntegrationTests.cs @@ -65,7 +65,7 @@ public async Task GetIntercomHashAsync_WithValidApiKey_ReturnsHash() // Act TorBoxResponse response = await _fixture.Client.Main.Notifications - .GetIntercomHashAsync(new GetIntercomHashOptions { AuthId = authId, Email = email }, cts.Token); + .GetIntercomHashAsync(authId, email, cts.Token); // Assert Assert.NotNull(response); diff --git a/tests/TorBoxSDK.IntegrationTests/Main/Torrents/TorrentsClientIntegrationTests.cs b/tests/TorBoxSDK.IntegrationTests/Main/Torrents/TorrentsClientIntegrationTests.cs index 5bbe32a..62032a4 100644 --- a/tests/TorBoxSDK.IntegrationTests/Main/Torrents/TorrentsClientIntegrationTests.cs +++ b/tests/TorBoxSDK.IntegrationTests/Main/Torrents/TorrentsClientIntegrationTests.cs @@ -43,7 +43,7 @@ public async Task CheckCachedAsync_WithKnownHash_ReturnsResponse() // Act TorBoxResponse response = await _fixture.Client.Main.Torrents - .CheckCachedAsync(new CheckCachedOptions { Hashes = hashes }, cancellationToken: cts.Token); + .CheckCachedAsync(hashes, cancellationToken: cts.Token); // Assert Assert.NotNull(response); @@ -62,7 +62,7 @@ public async Task GetTorrentInfoAsync_WithKnownHash_ReturnsTorrentInfo() // Act TorBoxResponse response = await _fixture.Client.Main.Torrents - .GetTorrentInfoAsync(new GetTorrentInfoOptions { Hash = hash }, cancellationToken: cts.Token); + .GetTorrentInfoAsync(hash, cancellationToken: cts.Token); // Assert Assert.NotNull(response); diff --git a/tests/TorBoxSDK.IntegrationTests/Main/Usenet/UsenetClientIntegrationTests.cs b/tests/TorBoxSDK.IntegrationTests/Main/Usenet/UsenetClientIntegrationTests.cs index 1bde4b7..e32c233 100644 --- a/tests/TorBoxSDK.IntegrationTests/Main/Usenet/UsenetClientIntegrationTests.cs +++ b/tests/TorBoxSDK.IntegrationTests/Main/Usenet/UsenetClientIntegrationTests.cs @@ -42,7 +42,7 @@ public async Task CheckCachedAsync_WithDummyHashes_ReturnsResponse() // Act TorBoxResponse response = await _fixture.Client.Main.Usenet - .CheckCachedAsync(new CheckCachedOptions { Hashes = hashes }, cancellationToken: cts.Token); + .CheckCachedAsync(hashes, cancellationToken: cts.Token); // Assert Assert.NotNull(response); diff --git a/tests/TorBoxSDK.IntegrationTests/Main/WebDownloads/WebDownloadsClientIntegrationTests.cs b/tests/TorBoxSDK.IntegrationTests/Main/WebDownloads/WebDownloadsClientIntegrationTests.cs index 1ac0caa..5c2e8c5 100644 --- a/tests/TorBoxSDK.IntegrationTests/Main/WebDownloads/WebDownloadsClientIntegrationTests.cs +++ b/tests/TorBoxSDK.IntegrationTests/Main/WebDownloads/WebDownloadsClientIntegrationTests.cs @@ -61,7 +61,7 @@ public async Task CheckCachedAsync_WithDummyHashes_ReturnsResponse() // Act TorBoxResponse response = await _fixture.Client.Main.WebDownloads - .CheckCachedAsync(new CheckCachedOptions { Hashes = hashes }, cancellationToken: cts.Token); + .CheckCachedAsync(hashes, cancellationToken: cts.Token); // Assert Assert.NotNull(response); diff --git a/tests/TorBoxSDK.IntegrationTests/Search/SearchApiClientIntegrationTests.cs b/tests/TorBoxSDK.IntegrationTests/Search/SearchApiClientIntegrationTests.cs index 9a8abe9..ddb77f7 100644 --- a/tests/TorBoxSDK.IntegrationTests/Search/SearchApiClientIntegrationTests.cs +++ b/tests/TorBoxSDK.IntegrationTests/Search/SearchApiClientIntegrationTests.cs @@ -129,7 +129,7 @@ public async Task SearchTorznabAsync_WithValidQuery_ReturnsResults() // Act TorBoxResponse response = await _fixture.Client.Search - .SearchTorznabAsync(new SearchTorznabOptions { Query = "ubuntu" }, cancellationToken: cts.Token); + .SearchTorznabAsync("ubuntu", cancellationToken: cts.Token); // Assert Assert.NotNull(response); @@ -146,7 +146,7 @@ public async Task SearchNewznabAsync_WithValidQuery_ReturnsResults() // Act TorBoxResponse response = await _fixture.Client.Search - .SearchNewznabAsync(new SearchNewznabOptions { Query = "ubuntu" }, cancellationToken: cts.Token); + .SearchNewznabAsync("ubuntu", cancellationToken: cts.Token); // Assert Assert.NotNull(response); diff --git a/tests/TorBoxSDK.SchemaValidationTests/Infrastructure/OpenApiSchemaReader.cs b/tests/TorBoxSDK.SchemaValidationTests/Infrastructure/OpenApiSchemaReader.cs index bfe1690..2eb09a5 100644 --- a/tests/TorBoxSDK.SchemaValidationTests/Infrastructure/OpenApiSchemaReader.cs +++ b/tests/TorBoxSDK.SchemaValidationTests/Infrastructure/OpenApiSchemaReader.cs @@ -4,38 +4,59 @@ namespace TorBoxSDK.SchemaValidationTests.Infrastructure; /// /// Reads and parses the TorBox OpenAPI specification to extract schema property definitions. +/// The specification is fetched from the public TorBox API endpoint and cached for the +/// lifetime of the process. /// internal static class OpenApiSchemaReader { /// - /// Reads the OpenAPI specification from the given file path and returns all schema definitions. + /// Public URL of the TorBox OpenAPI specification. + /// + internal const string OpenApiUrl = "https://api.torbox.app/openapi.json"; + + private static readonly HttpClient _httpClient = new(); + private static readonly Lazy>>> _cachedSchemas = new(FetchAndParseAsync); + + /// + /// Returns all schema definitions from the TorBox OpenAPI specification. + /// The specification is downloaded once from and cached + /// for the lifetime of the process. /// - /// Path to the open_api.json file. /// /// A dictionary keyed by schema name. Each value is a dictionary of property name /// to its OpenAPI type string (e.g., "string", "integer", /// "boolean", "array", "object", or a $ref type name). /// - public static IReadOnlyDictionary> Read(string filePath) + public static Task>> ReadFromApiAsync() => _cachedSchemas.Value; + + /// + /// Parses the OpenAPI specification from a raw JSON string. + /// + /// The raw JSON content of the OpenAPI specification. + public static IReadOnlyDictionary> Parse(string json) { - string json = File.ReadAllText(filePath); - using JsonDocument doc = JsonDocument.Parse(json); + using var doc = JsonDocument.Parse(json); - Dictionary> result = - new(StringComparer.Ordinal); + Dictionary> result = []; if (!doc.RootElement.TryGetProperty("components", out JsonElement components)) + { return result; + } if (!components.TryGetProperty("schemas", out JsonElement schemas)) + { return result; + } foreach (JsonProperty schema in schemas.EnumerateObject()) { if (!schema.Value.TryGetProperty("properties", out JsonElement props)) + { continue; + } - Dictionary properties = new(StringComparer.Ordinal); + Dictionary properties = []; foreach (JsonProperty prop in props.EnumerateObject()) { properties[prop.Name] = ExtractType(prop.Value); @@ -47,31 +68,23 @@ public static IReadOnlyDictionary> R return result; } - /// - /// Locates the open_api.json file next to the test assembly. - /// - /// - /// Thrown when open_api.json is not found in the output directory. - /// Ensure it is linked and configured with CopyToOutputDirectory in the csproj. - /// - public static string FindOpenApiFilePath() + private static async Task>> FetchAndParseAsync() { - string candidate = Path.Combine(AppContext.BaseDirectory, "open_api.json"); - return File.Exists(candidate) - ? candidate - : throw new FileNotFoundException( - $"open_api.json not found in '{AppContext.BaseDirectory}'. " + - "Ensure the file is linked and set to CopyToOutputDirectory in the csproj.", - candidate); + string json = await _httpClient.GetStringAsync(OpenApiUrl).ConfigureAwait(false); + return Parse(json); } private static string ExtractType(JsonElement propDef) { if (propDef.TryGetProperty("type", out JsonElement typeElem)) + { return typeElem.GetString() ?? "unknown"; + } if (propDef.TryGetProperty("$ref", out JsonElement refElem)) + { return refElem.GetString()?.Split('/').Last() ?? "unknown"; + } if (propDef.TryGetProperty("anyOf", out JsonElement anyOf)) { @@ -79,11 +92,17 @@ private static string ExtractType(JsonElement propDef) foreach (JsonElement item in anyOf.EnumerateArray()) { if (item.TryGetProperty("type", out JsonElement t)) + { parts.Add(t.GetString() ?? "null"); + } else if (item.TryGetProperty("$ref", out JsonElement r)) + { parts.Add(r.GetString()?.Split('/').Last() ?? "unknown"); + } else + { parts.Add("null"); + } } return string.Join("|", parts); } diff --git a/tests/TorBoxSDK.SchemaValidationTests/Infrastructure/SchemaLiveTestFixture.cs b/tests/TorBoxSDK.SchemaValidationTests/Infrastructure/SchemaLiveTestFixture.cs index c5b7e9a..4409f64 100644 --- a/tests/TorBoxSDK.SchemaValidationTests/Infrastructure/SchemaLiveTestFixture.cs +++ b/tests/TorBoxSDK.SchemaValidationTests/Infrastructure/SchemaLiveTestFixture.cs @@ -12,7 +12,7 @@ namespace TorBoxSDK.SchemaValidationTests.Infrastructure; /// public sealed class SchemaLiveTestFixture : IAsyncLifetime { - private static readonly Uri TorBoxBaseUri = new("https://api.torbox.app"); + private static readonly Uri _torBoxBaseUri = new("https://api.torbox.app"); private HttpClient? _httpClient; @@ -24,8 +24,7 @@ public sealed class SchemaLiveTestFixture : IAsyncLifetime /// /// Gets the configured to call the live TorBox API. /// - public HttpClient HttpClient => - _httpClient ?? throw new InvalidOperationException("Fixture has not been initialised."); + public HttpClient HttpClient => _httpClient ?? throw new InvalidOperationException("Fixture has not been initialised."); /// Initialises the fixture and creates the . public SchemaLiveTestFixture() @@ -33,7 +32,7 @@ public SchemaLiveTestFixture() string? apiKey = Environment.GetEnvironmentVariable("TORBOX_API_KEY"); HasApiKey = !string.IsNullOrWhiteSpace(apiKey); - _httpClient = new HttpClient { BaseAddress = TorBoxBaseUri }; + _httpClient = new HttpClient { BaseAddress = _torBoxBaseUri }; if (HasApiKey) { diff --git a/tests/TorBoxSDK.SchemaValidationTests/Infrastructure/SchemaModelMapping.cs b/tests/TorBoxSDK.SchemaValidationTests/Infrastructure/SchemaModelMapping.cs index 2ee98f2..c363f5f 100644 --- a/tests/TorBoxSDK.SchemaValidationTests/Infrastructure/SchemaModelMapping.cs +++ b/tests/TorBoxSDK.SchemaValidationTests/Infrastructure/SchemaModelMapping.cs @@ -34,39 +34,39 @@ internal static class SchemaModelMapping new Dictionary(StringComparer.Ordinal) { // ── RSS ───────────────────────────────────────────────────────────────────── - ["AddRss"] = typeof(AddRssRequest), + ["AddRss"] = typeof(AddRssRequest), ["ModifyRss"] = typeof(ModifyRssRequest), ["ControlRss"] = typeof(ControlRssRequest), // ── Integrations ───────────────────────────────────────────────────────── // All AddToXxx schemas share CreateIntegrationJobRequest for the common fields. // Service-specific auth tokens are listed in KnownOpenApiFieldsNotInSdk. - ["AddTo1Fichier"] = typeof(CreateIntegrationJobRequest), - ["AddToDropbox"] = typeof(CreateIntegrationJobRequest), - ["AddToGofile"] = typeof(CreateIntegrationJobRequest), + ["AddTo1Fichier"] = typeof(CreateIntegrationJobRequest), + ["AddToDropbox"] = typeof(CreateIntegrationJobRequest), + ["AddToGofile"] = typeof(CreateIntegrationJobRequest), ["AddToGoogleDrive"] = typeof(CreateIntegrationJobRequest), - ["AddToOnedrive"] = typeof(CreateIntegrationJobRequest), - ["AddToPixeldrain"] = typeof(CreateIntegrationJobRequest), - ["LinkedRolesData"] = typeof(LinkedRolesRequest), + ["AddToOnedrive"] = typeof(CreateIntegrationJobRequest), + ["AddToPixeldrain"] = typeof(CreateIntegrationJobRequest), + ["LinkedRolesData"] = typeof(LinkedRolesRequest), ["OAuthRegisterRequest"] = typeof(OAuthRegisterRequest), // ── User ───────────────────────────────────────────────────────────────── - ["BaseSettingsModel"] = typeof(UserSettings), + ["BaseSettingsModel"] = typeof(UserSettings), ["DeleteAccountSchema"] = typeof(DeleteAccountRequest), - ["DeviceTokenSchema"] = typeof(DeviceTokenRequest), - ["RefreshTokenSchema"] = typeof(RefreshTokenRequest), + ["DeviceTokenSchema"] = typeof(DeviceTokenRequest), + ["RefreshTokenSchema"] = typeof(RefreshTokenRequest), ["SearchEngineEditModel"] = typeof(ModifySearchEnginesRequest), - ["SearchEngineModel"] = typeof(AddSearchEnginesRequest), - ["ControlSearchEngine"] = typeof(ControlSearchEnginesRequest), + ["SearchEngineModel"] = typeof(AddSearchEnginesRequest), + ["ControlSearchEngine"] = typeof(ControlSearchEnginesRequest), // ── Torrents ───────────────────────────────────────────────────────────── ["Body_create_torrent_v1_api_torrents_createtorrent_post"] = typeof(CreateTorrentRequest), ["Body_async_create_torrent_v1_api_torrents_asynccreatetorrent_post"] = typeof(CreateTorrentRequest), - ["CheckCached"] = typeof(CheckCachedRequest), + ["CheckCached"] = typeof(CheckCachedRequest), ["ControlTorrent"] = typeof(ControlTorrentRequest), - ["EditTorrent"] = typeof(EditTorrentRequest), + ["EditTorrent"] = typeof(EditTorrentRequest), ["MagnetToTorrent"] = typeof(MagnetToFileRequest), ["Body_get_torrent_info_post_v1_api_torrents_torrentinfo_post"] = typeof(TorrentInfoRequest), @@ -77,7 +77,7 @@ internal static class SchemaModelMapping ["Body_async_create_usenet_download_v1_api_usenet_asynccreateusenetdownload_post"] = typeof(CreateUsenetDownloadRequest), ["ControlUsenetDownload"] = typeof(ControlUsenetDownloadRequest), - ["EditUsenetDownload"] = typeof(EditUsenetDownloadRequest), + ["EditUsenetDownload"] = typeof(EditUsenetDownloadRequest), // ── Web Downloads ───────────────────────────────────────────────────────── ["Body_create_web_download_v1_api_webdl_createwebdownload_post"] = @@ -85,7 +85,7 @@ internal static class SchemaModelMapping ["Body_async_create_web_download_v1_api_webdl_asynccreatewebdownload_post"] = typeof(CreateWebDownloadRequest), ["ControlWebDownload"] = typeof(ControlWebDownloadRequest), - ["EditWebDownload"] = typeof(EditWebDownloadRequest), + ["EditWebDownload"] = typeof(EditWebDownloadRequest), // ── Queued ─────────────────────────────────────────────────────────────── ["ControlQueuedDownload"] = typeof(ControlQueuedRequest), @@ -120,12 +120,12 @@ internal static class SchemaModelMapping new Dictionary>(StringComparer.Ordinal) { // Integration service-specific auth tokens are not part of the shared request model. - ["AddTo1Fichier"] = Set("onefichier_token"), - ["AddToDropbox"] = Set("dropbox_token"), - ["AddToGofile"] = Set("gofile_token"), + ["AddTo1Fichier"] = Set("onefichier_token"), + ["AddToDropbox"] = Set("dropbox_token"), + ["AddToGofile"] = Set("gofile_token"), ["AddToGoogleDrive"] = Set("google_token"), - ["AddToOnedrive"] = Set("onedrive_token"), - ["AddToPixeldrain"] = Set("pixeldrain_token"), + ["AddToOnedrive"] = Set("onedrive_token"), + ["AddToPixeldrain"] = Set("pixeldrain_token"), // File upload fields are decorated with [JsonIgnore] and sent as multipart form data. ["Body_create_torrent_v1_api_torrents_createtorrent_post"] = Set("file"), @@ -181,26 +181,55 @@ public static string MapDotNetTypeToOpenApiType(Type dotNetType) { Type underlying = Nullable.GetUnderlyingType(dotNetType) ?? dotNetType; - if (underlying == typeof(string)) return "string"; - if (underlying == typeof(bool)) return "boolean"; + if (underlying == typeof(string)) + { + return "string"; + } + + if (underlying == typeof(bool)) + { + return "boolean"; + } + if (underlying == typeof(int) || underlying == typeof(long) || underlying == typeof(short) || underlying == typeof(uint) || - underlying == typeof(ulong)) return "integer"; + underlying == typeof(ulong)) + { + return "integer"; + } + if (underlying == typeof(double) || underlying == typeof(float) || - underlying == typeof(decimal)) return "number"; + underlying == typeof(decimal)) + { + return "number"; + } + if (underlying == typeof(DateTimeOffset) || underlying == typeof(DateTime)) + { return "string"; // ISO-8601 strings in JSON + } // TorBoxJsonOptions.Default registers a global JsonStringEnumConverter with SnakeCaseLower, // so all enums serialize as strings regardless of per-type [JsonConverter] attributes. - if (underlying.IsEnum) return "string"; - if (IsCollectionType(underlying)) return "array"; + if (underlying.IsEnum) + { + return "string"; + } + + if (IsCollectionType(underlying)) + { + return "array"; + } return "object"; } private static bool IsCollectionType(Type type) { - if (!type.IsGenericType) return false; + if (!type.IsGenericType) + { + return false; + } + Type def = type.GetGenericTypeDefinition(); return def == typeof(IReadOnlyList<>) || def == typeof(List<>) @@ -209,6 +238,6 @@ private static bool IsCollectionType(Type type) || def == typeof(ICollection<>); } - private static IReadOnlySet Set(params string[] values) => - new HashSet(values, StringComparer.Ordinal); + private static HashSet Set(params string[] values) => + new(values, StringComparer.Ordinal); } diff --git a/tests/TorBoxSDK.SchemaValidationTests/Live/GeneralSchemaLiveTests.cs b/tests/TorBoxSDK.SchemaValidationTests/Live/GeneralSchemaLiveTests.cs index d7dd1ad..2b4a74d 100644 --- a/tests/TorBoxSDK.SchemaValidationTests/Live/GeneralSchemaLiveTests.cs +++ b/tests/TorBoxSDK.SchemaValidationTests/Live/GeneralSchemaLiveTests.cs @@ -1,4 +1,4 @@ -using TorBoxSDK.Models.Common; +using TorBoxSDK.Models.Common; using TorBoxSDK.Models.General; using TorBoxSDK.SchemaValidationTests.Infrastructure; @@ -53,7 +53,7 @@ public async Task Get30DayStats_ResponseFields_AllMappedInSdkModel() // Act using HttpResponseMessage response = await _fixture.HttpClient - .GetAsync("/v1/api/stats/30day", cts.Token) + .GetAsync("/v1/api/stats/30days", cts.Token) .ConfigureAwait(false); response.EnsureSuccessStatusCode(); @@ -68,7 +68,7 @@ public async Task Get30DayStats_ResponseFields_AllMappedInSdkModel() // Assert Assert.True( unmapped.Count == 0, - BuildMessage("GET /v1/api/stats/30day", typeof(DailyStats), unmapped)); + BuildMessage("GET /v1/api/stats/30days", typeof(DailyStats), unmapped)); } [SkippableFact] @@ -81,7 +81,7 @@ public async Task GetSpeedtestFiles_ResponseFields_AllMappedInSdkModel() // Act using HttpResponseMessage response = await _fixture.HttpClient - .GetAsync("/v1/api/general/speedtest", cts.Token) + .GetAsync("/v1/api/speedtest", cts.Token) .ConfigureAwait(false); response.EnsureSuccessStatusCode(); @@ -96,7 +96,7 @@ public async Task GetSpeedtestFiles_ResponseFields_AllMappedInSdkModel() // Assert Assert.True( unmapped.Count == 0, - BuildMessage("GET /v1/api/general/speedtest", typeof(SpeedtestServer), unmapped)); + BuildMessage("GET /v1/api/speedtest", typeof(SpeedtestServer), unmapped)); } [SkippableFact] @@ -109,7 +109,7 @@ public async Task GetChangelogsJson_ResponseFields_AllMappedInSdkModel() // Act using HttpResponseMessage response = await _fixture.HttpClient - .GetAsync("/v1/api/general/changelogs/json", cts.Token) + .GetAsync("/v1/api/changelogs/json", cts.Token) .ConfigureAwait(false); response.EnsureSuccessStatusCode(); @@ -124,7 +124,7 @@ public async Task GetChangelogsJson_ResponseFields_AllMappedInSdkModel() // Assert Assert.True( unmapped.Count == 0, - BuildMessage("GET /v1/api/general/changelogs/json", typeof(Changelog), unmapped)); + BuildMessage("GET /v1/api/changelogs/json", typeof(Changelog), unmapped)); } private static string BuildMessage( diff --git a/tests/TorBoxSDK.SchemaValidationTests/Live/RelaySchemaLiveTests.cs b/tests/TorBoxSDK.SchemaValidationTests/Live/RelaySchemaLiveTests.cs index 9013394..abe4a25 100644 --- a/tests/TorBoxSDK.SchemaValidationTests/Live/RelaySchemaLiveTests.cs +++ b/tests/TorBoxSDK.SchemaValidationTests/Live/RelaySchemaLiveTests.cs @@ -1,4 +1,3 @@ -using TorBoxSDK.Models.Common; using TorBoxSDK.Models.Relay; using TorBoxSDK.SchemaValidationTests.Infrastructure; @@ -25,7 +24,7 @@ public async Task GetRelayStatus_ResponseFields_AllMappedInSdkModel() // Act using HttpResponseMessage response = await _fixture.HttpClient - .GetAsync("/v1/api/relay/status", cts.Token) + .GetAsync("https://relay.torbox.app/", cts.Token) .ConfigureAwait(false); response.EnsureSuccessStatusCode(); @@ -35,12 +34,12 @@ public async Task GetRelayStatus_ResponseFields_AllMappedInSdkModel() .ConfigureAwait(false); IReadOnlyList unmapped = - UnmappedFieldDetector.FindUnmappedFields>(json); + UnmappedFieldDetector.FindUnmappedFields(json); // Assert Assert.True( unmapped.Count == 0, - BuildMessage("GET /v1/api/relay/status", typeof(RelayStatus), unmapped)); + BuildMessage("GET https://relay.torbox.app/", typeof(RelayStatus), unmapped)); } private static string BuildMessage( diff --git a/tests/TorBoxSDK.SchemaValidationTests/Live/SearchSchemaLiveTests.cs b/tests/TorBoxSDK.SchemaValidationTests/Live/SearchSchemaLiveTests.cs index 71898da..5cf530d 100644 --- a/tests/TorBoxSDK.SchemaValidationTests/Live/SearchSchemaLiveTests.cs +++ b/tests/TorBoxSDK.SchemaValidationTests/Live/SearchSchemaLiveTests.cs @@ -1,4 +1,4 @@ -using TorBoxSDK.Models.Common; +using TorBoxSDK.Models.Common; using TorBoxSDK.Models.Search; using TorBoxSDK.SchemaValidationTests.Infrastructure; @@ -90,13 +90,10 @@ public async Task SearchMeta_ResponseFields_AllMappedInSdkModel() .ReadAsStringAsync(cts.Token) .ConfigureAwait(false); - IReadOnlyList unmapped = - UnmappedFieldDetector.FindUnmappedFields>>(json); + IReadOnlyList unmapped = UnmappedFieldDetector.FindUnmappedFields>>(json); // Assert - Assert.True( - unmapped.Count == 0, - BuildMessage("GET /v1/api/search/meta/inception", typeof(MetaSearchResult), unmapped)); + Assert.True(unmapped.Count == 0, BuildMessage("GET /v1/api/search/meta/inception", typeof(MetaSearchResult), unmapped)); } private static string BuildMessage( diff --git a/tests/TorBoxSDK.SchemaValidationTests/OpenApi/OpenApiFieldCoverageTests.cs b/tests/TorBoxSDK.SchemaValidationTests/OpenApi/OpenApiFieldCoverageTests.cs index a0a5a95..9d4dd72 100644 --- a/tests/TorBoxSDK.SchemaValidationTests/OpenApi/OpenApiFieldCoverageTests.cs +++ b/tests/TorBoxSDK.SchemaValidationTests/OpenApi/OpenApiFieldCoverageTests.cs @@ -4,7 +4,7 @@ namespace TorBoxSDK.SchemaValidationTests.OpenApi; /// /// Verifies bidirectional field coverage between the TorBox OpenAPI specification -/// (open_api.json) and the corresponding SDK model types. +/// (fetched from https://api.torbox.app/openapi.json) and the corresponding SDK model types. /// /// /// @@ -25,19 +25,19 @@ namespace TorBoxSDK.SchemaValidationTests.OpenApi; /// public sealed class OpenApiFieldCoverageTests { - private static readonly string OpenApiFilePath = OpenApiSchemaReader.FindOpenApiFilePath(); - - private static readonly IReadOnlyDictionary> Schemas = - OpenApiSchemaReader.Read(OpenApiFilePath); + private static readonly Lazy>>> _schemas = new(OpenApiSchemaReader.ReadFromApiAsync); /// /// Provides one row per schema→type mapping for the parameterised tests. /// public static TheoryData MappedSchemas() { - TheoryData data = new(); + TheoryData data = []; foreach ((string schemaName, Type modelType) in SchemaModelMapping.SchemaToType) + { data.Add(schemaName, modelType); + } + return data; } @@ -47,19 +47,21 @@ public static TheoryData MappedSchemas() /// [Theory] [MemberData(nameof(MappedSchemas))] - public void OpenApiSchema_AllFieldsMappedInSdk(string schemaName, Type modelType) + public async Task OpenApiSchema_AllFieldsMappedInSdk(string schemaName, Type modelType) { // Arrange + IReadOnlyDictionary> schemas = await _schemas.Value; Assert.True( - Schemas.TryGetValue(schemaName, out IReadOnlyDictionary? schemaFields), - $"Schema '{schemaName}' was not found in open_api.json."); + schemas.TryGetValue(schemaName, out IReadOnlyDictionary? schemaFields), + $"Schema '{schemaName}' was not found in the OpenAPI specification."); + Assert.NotNull(schemaFields); IReadOnlySet sdkJsonNames = ModelReflector.GetJsonPropertyNames(modelType); IReadOnlySet knownAbsent = SchemaModelMapping.KnownOpenApiFieldsNotInSdk .GetValueOrDefault(schemaName) ?? new HashSet(); // Act - List missingInSdk = schemaFields!.Keys + List missingInSdk = schemaFields.Keys .Where(field => !sdkJsonNames.Contains(field) && !knownAbsent.Contains(field)) .OrderBy(f => f, StringComparer.Ordinal) .ToList(); @@ -78,12 +80,14 @@ public void OpenApiSchema_AllFieldsMappedInSdk(string schemaName, Type modelType /// [Theory] [MemberData(nameof(MappedSchemas))] - public void SdkModel_NoExtraFieldsBeyondOpenApiSchema(string schemaName, Type modelType) + public async Task SdkModel_NoExtraFieldsBeyondOpenApiSchema(string schemaName, Type modelType) { // Arrange + IReadOnlyDictionary> schemas = await _schemas.Value; Assert.True( - Schemas.TryGetValue(schemaName, out IReadOnlyDictionary? schemaFields), - $"Schema '{schemaName}' was not found in open_api.json."); + schemas.TryGetValue(schemaName, out IReadOnlyDictionary? schemaFields), + $"Schema '{schemaName}' was not found in the OpenAPI specification."); + Assert.NotNull(schemaFields); IReadOnlySet sdkJsonNames = ModelReflector.GetJsonPropertyNames(modelType); IReadOnlySet knownExtra = SchemaModelMapping.KnownSdkFieldsNotInOpenApi @@ -91,7 +95,7 @@ public void SdkModel_NoExtraFieldsBeyondOpenApiSchema(string schemaName, Type mo // Act List extraInSdk = sdkJsonNames - .Where(field => !schemaFields!.ContainsKey(field) && !knownExtra.Contains(field)) + .Where(field => !schemaFields.ContainsKey(field) && !knownExtra.Contains(field)) .OrderBy(f => f, StringComparer.Ordinal) .ToList(); diff --git a/tests/TorBoxSDK.SchemaValidationTests/OpenApi/OpenApiTypeMappingTests.cs b/tests/TorBoxSDK.SchemaValidationTests/OpenApi/OpenApiTypeMappingTests.cs index 66cd8e4..48a2ed5 100644 --- a/tests/TorBoxSDK.SchemaValidationTests/OpenApi/OpenApiTypeMappingTests.cs +++ b/tests/TorBoxSDK.SchemaValidationTests/OpenApi/OpenApiTypeMappingTests.cs @@ -15,44 +15,17 @@ namespace TorBoxSDK.SchemaValidationTests.OpenApi; /// public sealed class OpenApiTypeMappingTests { - private static readonly string OpenApiFilePath = OpenApiSchemaReader.FindOpenApiFilePath(); - - private static readonly IReadOnlyDictionary> Schemas = - OpenApiSchemaReader.Read(OpenApiFilePath); + private static readonly Lazy>>> _schemas = new(OpenApiSchemaReader.ReadFromApiAsync); /// - /// Provides one row per (schema, type, fieldName, openApiType) tuple for the parameterised test. - /// Only fields that are actually mapped in both the spec and the SDK are included. + /// Provides one row per schema→type mapping for the parameterised tests. /// - public static TheoryData MappedFields() + public static TheoryData MappedSchemas() { - TheoryData data = new(); - + TheoryData data = []; foreach ((string schemaName, Type modelType) in SchemaModelMapping.SchemaToType) { - if (!Schemas.TryGetValue(schemaName, out IReadOnlyDictionary? schemaFields)) - continue; - - IReadOnlyDictionary propMap = - ModelReflector.GetJsonPropertyMap(modelType); - - IReadOnlySet knownAbsent = SchemaModelMapping.KnownOpenApiFieldsNotInSdk - .GetValueOrDefault(schemaName) ?? new HashSet(); - - foreach ((string fieldName, string openApiType) in schemaFields) - { - // Skip fields intentionally not mapped in the SDK. - if (!propMap.ContainsKey(fieldName) || knownAbsent.Contains(fieldName)) - continue; - - // Skip fields with known type mismatches between OpenAPI spec and SDK serialization. - IReadOnlySet knownMismatch = SchemaModelMapping.KnownTypeMismatches - .GetValueOrDefault(schemaName) ?? new HashSet(); - if (knownMismatch.Contains(fieldName)) - continue; - - data.Add(schemaName, modelType, fieldName, openApiType); - } + data.Add(schemaName, modelType); } return data; @@ -63,27 +36,49 @@ public static TheoryData MappedFields() /// type category declared for the same field. /// [Theory] - [MemberData(nameof(MappedFields))] - public void MappedField_HasCompatibleType( - string schemaName, - Type modelType, - string fieldName, - string openApiType) + [MemberData(nameof(MappedSchemas))] + public async Task MappedSchemaFields_HaveCompatibleTypes(string schemaName, Type modelType) { // Arrange + IReadOnlyDictionary> schemas = await _schemas.Value; + Assert.True( + schemas.TryGetValue(schemaName, out IReadOnlyDictionary? schemaFields), + $"Schema '{schemaName}' was not found in the OpenAPI specification."); + Assert.NotNull(schemaFields); + IReadOnlyDictionary propMap = ModelReflector.GetJsonPropertyMap(modelType); - PropertyInfo prop = propMap[fieldName]; + IReadOnlySet knownAbsent = SchemaModelMapping.KnownOpenApiFieldsNotInSdk + .GetValueOrDefault(schemaName) ?? new HashSet(); + IReadOnlySet knownMismatch = SchemaModelMapping.KnownTypeMismatches + .GetValueOrDefault(schemaName) ?? new HashSet(); + + // Act + List mismatches = []; + foreach ((string fieldName, string openApiType) in schemaFields) + { + if (!propMap.TryGetValue(fieldName, out PropertyInfo? prop) || + knownAbsent.Contains(fieldName) || + knownMismatch.Contains(fieldName)) + { + continue; + } - string dotNetCategory = SchemaModelMapping.MapDotNetTypeToOpenApiType(prop.PropertyType); - string baseOpenApiType = ExtractBaseType(openApiType); + string dotNetCategory = SchemaModelMapping.MapDotNetTypeToOpenApiType(prop.PropertyType); + string baseOpenApiType = ExtractBaseType(openApiType); + if (!AreCompatible(dotNetCategory, baseOpenApiType)) + { + mismatches.Add( + $" - {schemaName}.{fieldName}: OpenAPI '{openApiType}' vs " + + $"{modelType.Name}.{prop.Name} ({prop.PropertyType.Name}) -> '{dotNetCategory}'"); + } + } - // Act & Assert + // Assert Assert.True( - AreCompatible(dotNetCategory, baseOpenApiType), - $"Type mismatch for '{schemaName}.{fieldName}': " + - $"OpenAPI declares '{openApiType}' but '{modelType.Name}.{prop.Name}' " + - $"({prop.PropertyType.Name}) maps to OpenAPI category '{dotNetCategory}'."); + mismatches.Count == 0, + $"Type mismatch(es) found for schema '{schemaName}':{Environment.NewLine}" + + string.Join(Environment.NewLine, mismatches)); } // ── Private helpers ─────────────────────────────────────────────────────────────── @@ -95,7 +90,9 @@ public void MappedField_HasCompatibleType( private static string ExtractBaseType(string openApiType) { if (!openApiType.Contains('|')) + { return openApiType; + } string? nonNull = openApiType .Split('|') @@ -111,12 +108,16 @@ private static string ExtractBaseType(string openApiType) private static bool AreCompatible(string dotNetCategory, string openApiBase) { if (string.Equals(dotNetCategory, openApiBase, StringComparison.Ordinal)) + { return true; + } // integer ↔ number are both numeric in JSON — allow either direction. if ((dotNetCategory == "integer" && openApiBase == "number") || (dotNetCategory == "number" && openApiBase == "integer")) + { return true; + } // DateTimeOffset is serialised as an ISO-8601 string, which is "string" in OpenAPI. // Enum types also serialise as strings. @@ -127,7 +128,9 @@ private static bool AreCompatible(string dotNetCategory, string openApiBase) openApiBase != "number" && openApiBase != "array" && openApiBase != "object") + { return true; + } return false; } diff --git a/tests/TorBoxSDK.SchemaValidationTests/TorBoxSDK.SchemaValidationTests.csproj b/tests/TorBoxSDK.SchemaValidationTests/TorBoxSDK.SchemaValidationTests.csproj index e8c66b7..3982904 100644 --- a/tests/TorBoxSDK.SchemaValidationTests/TorBoxSDK.SchemaValidationTests.csproj +++ b/tests/TorBoxSDK.SchemaValidationTests/TorBoxSDK.SchemaValidationTests.csproj @@ -6,13 +6,6 @@ true - - - - PreserveNewest - - - diff --git a/tests/TorBoxSDK.TestUtilities/ModelReflector.cs b/tests/TorBoxSDK.TestUtilities/ModelReflector.cs index b0ef887..628cd92 100644 --- a/tests/TorBoxSDK.TestUtilities/ModelReflector.cs +++ b/tests/TorBoxSDK.TestUtilities/ModelReflector.cs @@ -19,13 +19,15 @@ public static class ModelReflector /// public static IReadOnlySet GetJsonPropertyNames(Type type) { - HashSet names = new(StringComparer.Ordinal); + HashSet names = []; foreach (PropertyInfo prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { JsonPropertyNameAttribute? attr = prop.GetCustomAttribute(); if (attr is not null) + { names.Add(attr.Name); + } } return names; @@ -41,13 +43,15 @@ public static IReadOnlySet GetJsonPropertyNames(Type type) /// public static IReadOnlyDictionary GetJsonPropertyMap(Type type) { - Dictionary map = new(StringComparer.Ordinal); + Dictionary map = []; foreach (PropertyInfo prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { JsonPropertyNameAttribute? attr = prop.GetCustomAttribute(); if (attr is not null) + { map[attr.Name] = prop; + } } return map; diff --git a/tests/TorBoxSDK.TestUtilities/UnmappedFieldDetector.cs b/tests/TorBoxSDK.TestUtilities/UnmappedFieldDetector.cs index ea19f62..f296c4a 100644 --- a/tests/TorBoxSDK.TestUtilities/UnmappedFieldDetector.cs +++ b/tests/TorBoxSDK.TestUtilities/UnmappedFieldDetector.cs @@ -29,7 +29,7 @@ public static class UnmappedFieldDetector /// public static IReadOnlyList FindUnmappedFields(string json) { - using JsonDocument doc = JsonDocument.Parse(json); + using var doc = JsonDocument.Parse(json); List unmapped = []; TraverseElement(doc.RootElement, typeof(T), pathPrefix: string.Empty, unmapped); return unmapped; @@ -44,7 +44,9 @@ private static void TraverseElement( List unmapped) { if (element.ValueKind != JsonValueKind.Object) + { return; + } IReadOnlyDictionary knownProps = ModelReflector.GetJsonPropertyMap(targetType); @@ -80,7 +82,10 @@ private static void TraverseElement( foreach (JsonElement item in jsonProp.Value.EnumerateArray()) { if (item.ValueKind == JsonValueKind.Object) + { TraverseElement(item, elementType, $"{currentPath}[]", unmapped); + } + break; } } @@ -91,17 +96,17 @@ private static void TraverseElement( private static Type? GetCollectionElementType(Type type) { - if (!type.IsGenericType) return null; - - Type def = type.GetGenericTypeDefinition(); - if (def == typeof(IReadOnlyList<>) || def == typeof(List<>) || - def == typeof(IEnumerable<>) || def == typeof(IReadOnlyCollection<>) || - def == typeof(ICollection<>)) + if (!type.IsGenericType) { - return type.GetGenericArguments()[0]; + return null; } - return null; + Type def = type.GetGenericTypeDefinition(); + return def == typeof(IReadOnlyList<>) || def == typeof(List<>) || + def == typeof(IEnumerable<>) || def == typeof(IReadOnlyCollection<>) || + def == typeof(ICollection<>) + ? type.GetGenericArguments()[0] + : null; } /// diff --git a/tests/TorboxSDK.UnitTests/Main/General/GeneralClientTests.cs b/tests/TorboxSDK.UnitTests/Main/General/GeneralClientTests.cs index 31ba192..b8e3529 100644 --- a/tests/TorboxSDK.UnitTests/Main/General/GeneralClientTests.cs +++ b/tests/TorboxSDK.UnitTests/Main/General/GeneralClientTests.cs @@ -95,7 +95,7 @@ public sealed class GeneralClientTests "detail": "Found.", "data": [ { - "id": 1, + "id": "1", "name": "v8.4.3", "html": "

Major update

", "markdown": "**Major update**", @@ -257,7 +257,7 @@ public async Task GetChangelogsJsonAsync_WithNoParameters_SendsGetRequest() Assert.NotNull(result.Data); Assert.NotEmpty(result.Data); Changelog first = result.Data[0]; - Assert.Equal(1, first.Id); + Assert.Equal("1", first.Id); Assert.Equal("v8.4.3", first.Name); Assert.Equal("

Major update

", first.Html); Assert.Equal("**Major update**", first.Markdown); diff --git a/tests/TorboxSDK.UnitTests/Main/Notifications/NotificationsClientTests.cs b/tests/TorboxSDK.UnitTests/Main/Notifications/NotificationsClientTests.cs index c910eb7..1bae349 100644 --- a/tests/TorboxSDK.UnitTests/Main/Notifications/NotificationsClientTests.cs +++ b/tests/TorboxSDK.UnitTests/Main/Notifications/NotificationsClientTests.cs @@ -168,7 +168,7 @@ public async Task GetIntercomHashAsync_WithAuthIdAndEmail_SendsGetRequest() (NotificationsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(IntercomHashJson); // Act - TorBoxResponse result = await client.GetIntercomHashAsync(new GetIntercomHashOptions { AuthId = "auth-id", Email = "test@test.com" }); + TorBoxResponse result = await client.GetIntercomHashAsync("auth-id", "test@test.com"); // Assert Assert.NotNull(handler.LastRequest); @@ -180,13 +180,13 @@ public async Task GetIntercomHashAsync_WithAuthIdAndEmail_SendsGetRequest() } [Fact] - public async Task GetIntercomHashAsync_WithNullOptions_ThrowsArgumentNullException() + public async Task GetIntercomHashAsync_WithNullAuthId_ThrowsArgumentNullException() { // Arrange (NotificationsClient client, _) = ClientTestBase.CreateClient(IntercomHashJson); // Act & Assert - await Assert.ThrowsAsync(() => client.GetIntercomHashAsync(null!)); + await Assert.ThrowsAsync(() => client.GetIntercomHashAsync(null!, "test@test.com")); } [Fact] @@ -196,7 +196,7 @@ public async Task GetIntercomHashAsync_WithEmptyAuthId_ThrowsArgumentException() (NotificationsClient client, _) = ClientTestBase.CreateClient(IntercomHashJson); // Act & Assert - await Assert.ThrowsAsync(() => client.GetIntercomHashAsync(new GetIntercomHashOptions { AuthId = string.Empty, Email = "test@test.com" })); + await Assert.ThrowsAsync(() => client.GetIntercomHashAsync(string.Empty, "test@test.com")); } [Fact] @@ -206,6 +206,6 @@ public async Task GetIntercomHashAsync_WithEmptyEmail_ThrowsArgumentException() (NotificationsClient client, _) = ClientTestBase.CreateClient(IntercomHashJson); // Act & Assert - await Assert.ThrowsAsync(() => client.GetIntercomHashAsync(new GetIntercomHashOptions { AuthId = "auth-id", Email = string.Empty })); + await Assert.ThrowsAsync(() => client.GetIntercomHashAsync("auth-id", string.Empty)); } } diff --git a/tests/TorboxSDK.UnitTests/Main/Stream/StreamClientTests.cs b/tests/TorboxSDK.UnitTests/Main/Stream/StreamClientTests.cs index 36691c8..213eba7 100644 --- a/tests/TorboxSDK.UnitTests/Main/Stream/StreamClientTests.cs +++ b/tests/TorboxSDK.UnitTests/Main/Stream/StreamClientTests.cs @@ -36,7 +36,7 @@ public async Task CreateStreamAsync_WithRequiredParams_SendsCorrectUrl() (StreamClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(StreamUrlJson); // Act - TorBoxResponse result = await client.CreateStreamAsync(new CreateStreamOptions { Id = 42, FileId = 5, Type = "torrent" }); + TorBoxResponse result = await client.CreateStreamAsync(42, 5, "torrent"); // Assert Assert.NotNull(handler.LastRequest); @@ -56,7 +56,7 @@ public async Task CreateStreamAsync_WithAllOptionalParams_IncludesInQueryString( (StreamClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(StreamUrlJson); // Act - await client.CreateStreamAsync(new CreateStreamOptions { Id = 1, FileId = 2, Type = "usenet", ChosenSubtitleIndex = 3, ChosenAudioIndex = 1, ChosenResolutionIndex = 720 }); + await client.CreateStreamAsync(1, 2, "usenet", new CreateStreamOptions { ChosenSubtitleIndex = 3, ChosenAudioIndex = 1, ChosenResolutionIndex = 720 }); // Assert Assert.NotNull(handler.LastRequest); @@ -73,7 +73,7 @@ public async Task CreateStreamAsync_WithNullOptionalParams_OmitsFromQueryString( (StreamClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(StreamUrlJson); // Act - await client.CreateStreamAsync(new CreateStreamOptions { Id = 1, FileId = 2, Type = "torrent" }); + await client.CreateStreamAsync(1, 2, "torrent"); // Assert Assert.NotNull(handler.LastRequest); @@ -83,16 +83,6 @@ public async Task CreateStreamAsync_WithNullOptionalParams_OmitsFromQueryString( Assert.DoesNotContain("chosen_resolution_index", url); } - [Fact] - public async Task CreateStreamAsync_WithNullOptions_ThrowsArgumentNullException() - { - // Arrange - (StreamClient client, _) = ClientTestBase.CreateClient(StreamUrlJson); - - // Act & Assert - await Assert.ThrowsAsync(() => client.CreateStreamAsync(null!)); - } - [Fact] public async Task CreateStreamAsync_WithEmptyType_ThrowsArgumentException() { @@ -100,7 +90,7 @@ public async Task CreateStreamAsync_WithEmptyType_ThrowsArgumentException() (StreamClient client, _) = ClientTestBase.CreateClient(StreamUrlJson); // Act & Assert - await Assert.ThrowsAsync(() => client.CreateStreamAsync(new CreateStreamOptions { Id = 1, FileId = 2, Type = string.Empty })); + await Assert.ThrowsAsync(() => client.CreateStreamAsync(1, 2, string.Empty)); } [Fact] @@ -110,7 +100,7 @@ public async Task GetStreamDataAsync_WithRequiredParams_SendsCorrectUrl() (StreamClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(StreamDataJson); // Act - TorBoxResponse result = await client.GetStreamDataAsync(new GetStreamDataOptions { PresignedToken = "pre-signed-token-abc", Token = "auth-token-123" }); + TorBoxResponse result = await client.GetStreamDataAsync("pre-signed-token-abc", "auth-token-123"); // Assert Assert.NotNull(handler.LastRequest); @@ -129,7 +119,7 @@ public async Task GetStreamDataAsync_WithAllOptionalParams_IncludesInQueryString (StreamClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(StreamDataJson); // Act - await client.GetStreamDataAsync(new GetStreamDataOptions { PresignedToken = "presigned", Token = "token", ChosenSubtitleIndex = 0, ChosenAudioIndex = 2, ChosenResolutionIndex = 1080 }); + await client.GetStreamDataAsync("presigned", "token", new GetStreamDataOptions { ChosenSubtitleIndex = 0, ChosenAudioIndex = 2, ChosenResolutionIndex = 1080 }); // Assert Assert.NotNull(handler.LastRequest); @@ -146,7 +136,7 @@ public async Task GetStreamDataAsync_WithNullOptionalParams_OmitsFromQueryString (StreamClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(StreamDataJson); // Act - await client.GetStreamDataAsync(new GetStreamDataOptions { PresignedToken = "presigned", Token = "token" }); + await client.GetStreamDataAsync("presigned", "token"); // Assert Assert.NotNull(handler.LastRequest); @@ -157,13 +147,13 @@ public async Task GetStreamDataAsync_WithNullOptionalParams_OmitsFromQueryString } [Fact] - public async Task GetStreamDataAsync_WithNullOptions_ThrowsArgumentNullException() + public async Task GetStreamDataAsync_WithNullPresignedToken_ThrowsArgumentNullException() { // Arrange (StreamClient client, _) = ClientTestBase.CreateClient(StreamDataJson); // Act & Assert - await Assert.ThrowsAsync(() => client.GetStreamDataAsync(null!)); + await Assert.ThrowsAsync(() => client.GetStreamDataAsync(null!, "token")); } [Fact] @@ -173,7 +163,7 @@ public async Task GetStreamDataAsync_WithEmptyPresignedToken_ThrowsArgumentExcep (StreamClient client, _) = ClientTestBase.CreateClient(StreamDataJson); // Act & Assert - await Assert.ThrowsAsync(() => client.GetStreamDataAsync(new GetStreamDataOptions { PresignedToken = string.Empty, Token = "token" })); + await Assert.ThrowsAsync(() => client.GetStreamDataAsync(string.Empty, "token")); } [Fact] @@ -183,6 +173,6 @@ public async Task GetStreamDataAsync_WithEmptyToken_ThrowsArgumentException() (StreamClient client, _) = ClientTestBase.CreateClient(StreamDataJson); // Act & Assert - await Assert.ThrowsAsync(() => client.GetStreamDataAsync(new GetStreamDataOptions { PresignedToken = "presigned", Token = string.Empty })); + await Assert.ThrowsAsync(() => client.GetStreamDataAsync("presigned", string.Empty)); } } diff --git a/tests/TorboxSDK.UnitTests/Main/Torrents/TorrentsClientTests.cs b/tests/TorboxSDK.UnitTests/Main/Torrents/TorrentsClientTests.cs index 24b4534..3548165 100644 --- a/tests/TorboxSDK.UnitTests/Main/Torrents/TorrentsClientTests.cs +++ b/tests/TorboxSDK.UnitTests/Main/Torrents/TorrentsClientTests.cs @@ -246,10 +246,10 @@ public async Task RequestDownloadAsync_WithValidOptions_SendsGetRequest() { // Arrange (TorrentsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(DownloadJson); - RequestDownloadOptions options = new() { TorrentId = 1 }; + RequestDownloadOptions options = new() { }; // Act - TorBoxResponse result = await client.RequestDownloadAsync(options); + TorBoxResponse result = await client.RequestDownloadAsync(1, options); // Assert Assert.NotNull(handler.LastRequest); @@ -260,13 +260,17 @@ public async Task RequestDownloadAsync_WithValidOptions_SendsGetRequest() } [Fact] - public async Task RequestDownloadAsync_WithNullOptions_ThrowsArgumentNullException() + public async Task RequestDownloadAsync_WithNoOptions_SendsGetRequestWithIdOnly() { // Arrange - (TorrentsClient client, _) = ClientTestBase.CreateClient(DownloadJson); + (TorrentsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(DownloadJson); - // Act & Assert - await Assert.ThrowsAsync(() => client.RequestDownloadAsync(null!)); + // Act + await client.RequestDownloadAsync(1); + + // Assert + Assert.NotNull(handler.LastRequest); + Assert.Contains("torrent_id=1", handler.LastRequest.RequestUri!.ToString()); } [Fact] @@ -303,7 +307,7 @@ public async Task GetTorrentInfoAsync_WithHash_SendsGetRequest() (TorrentsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(TorrentInfoJson); // Act - TorBoxResponse result = await client.GetTorrentInfoAsync(new GetTorrentInfoOptions { Hash = "abc123def456" }); + TorBoxResponse result = await client.GetTorrentInfoAsync("abc123def456"); // Assert Assert.NotNull(handler.LastRequest); @@ -321,7 +325,7 @@ public async Task GetTorrentInfoAsync_WithUseCacheLookup_IncludesInQueryString() (TorrentsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(TorrentInfoJson); // Act - await client.GetTorrentInfoAsync(new GetTorrentInfoOptions { Hash = "abc123def456", Timeout = 60, UseCacheLookup = true }); + await client.GetTorrentInfoAsync("abc123def456", new GetTorrentInfoOptions { Timeout = 60, UseCacheLookup = true }); // Assert Assert.NotNull(handler.LastRequest); @@ -332,7 +336,7 @@ public async Task GetTorrentInfoAsync_WithUseCacheLookup_IncludesInQueryString() } [Fact] - public async Task GetTorrentInfoAsync_WithNullOptions_ThrowsArgumentNullException() + public async Task GetTorrentInfoAsync_WithNullHash_ThrowsArgumentNullException() { // Arrange (TorrentsClient client, _) = ClientTestBase.CreateClient(TorrentInfoJson); @@ -348,7 +352,7 @@ public async Task GetTorrentInfoAsync_WithEmptyHash_ThrowsArgumentException() (TorrentsClient client, _) = ClientTestBase.CreateClient(TorrentInfoJson); // Act & Assert - await Assert.ThrowsAsync(() => client.GetTorrentInfoAsync(new GetTorrentInfoOptions { Hash = string.Empty })); + await Assert.ThrowsAsync(() => client.GetTorrentInfoAsync(string.Empty)); } [Fact] @@ -358,7 +362,7 @@ public async Task GetTorrentInfoAsync_WithNullUseCacheLookup_OmitsFromQueryStrin (TorrentsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(TorrentInfoJson); // Act - await client.GetTorrentInfoAsync(new GetTorrentInfoOptions { Hash = "abc123def456" }); + await client.GetTorrentInfoAsync("abc123def456"); // Assert Assert.NotNull(handler.LastRequest); @@ -428,14 +432,13 @@ public async Task RequestDownloadAsync_WithNewOptions_IncludesInQueryString() (TorrentsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(DownloadJson); RequestDownloadOptions options = new() { - TorrentId = 42, Token = "custom-token", AppendName = true, Redirect = false, }; // Act - await client.RequestDownloadAsync(options); + await client.RequestDownloadAsync(42, options); // Assert Assert.NotNull(handler.LastRequest); @@ -453,7 +456,7 @@ public async Task ExportDataAsync_WithTorrentId_SendsGetRequest() (TorrentsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(ExportDataJson); // Act - TorBoxResponse result = await client.ExportDataAsync(new ExportDataOptions { TorrentId = 42 }); + TorBoxResponse result = await client.ExportDataAsync(42); // Assert Assert.NotNull(handler.LastRequest); @@ -464,13 +467,18 @@ public async Task ExportDataAsync_WithTorrentId_SendsGetRequest() } [Fact] - public async Task ExportDataAsync_WithNullOptions_ThrowsArgumentNullException() + public async Task ExportDataAsync_WithExportType_IncludesInQueryString() { // Arrange - (TorrentsClient client, _) = ClientTestBase.CreateClient(ExportDataJson); + (TorrentsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(ExportDataJson); - // Act & Assert - await Assert.ThrowsAsync(() => client.ExportDataAsync(null!)); + // Act + await client.ExportDataAsync(42, "magnet"); + + // Assert + Assert.NotNull(handler.LastRequest); + string url = handler.LastRequest.RequestUri!.ToString(); + Assert.Contains("torrent_id=42", url); } // --- CheckCachedAsync --- @@ -490,7 +498,7 @@ public async Task CheckCachedAsync_WithHashes_SendsGetRequest() (TorrentsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(cachedJson); // Act - await client.CheckCachedAsync(new CheckCachedOptions { Hashes = ["hash1", "hash2"] }); + await client.CheckCachedAsync(["hash1", "hash2"]); // Assert Assert.NotNull(handler.LastRequest); @@ -514,7 +522,7 @@ public async Task CheckCachedAsync_WithAllParams_IncludesInQueryString() (TorrentsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(cachedJson); // Act - await client.CheckCachedAsync(new CheckCachedOptions { Hashes = ["hash1"], Format = "object", ListFiles = true }); + await client.CheckCachedAsync(["hash1"], new CheckCachedOptions { Format = "object", ListFiles = true }); // Assert Assert.NotNull(handler.LastRequest); @@ -530,7 +538,7 @@ public async Task CheckCachedAsync_WithNullOptions_ThrowsArgumentNullException() (TorrentsClient client, _) = ClientTestBase.CreateClient(SuccessJson); // Act & Assert - await Assert.ThrowsAsync(() => client.CheckCachedAsync(null!)); + await Assert.ThrowsAsync(() => client.CheckCachedAsync((IReadOnlyList)null!)); } [Fact] @@ -540,7 +548,7 @@ public async Task CheckCachedAsync_WithNullHashes_ThrowsArgumentNullException() (TorrentsClient client, _) = ClientTestBase.CreateClient(SuccessJson); // Act & Assert - await Assert.ThrowsAsync(() => client.CheckCachedAsync(new CheckCachedOptions { Hashes = null! })); + await Assert.ThrowsAsync(() => client.CheckCachedAsync((IReadOnlyList)null!)); } // --- CheckCachedByPostAsync --- diff --git a/tests/TorboxSDK.UnitTests/Main/Usenet/UsenetClientTests.cs b/tests/TorboxSDK.UnitTests/Main/Usenet/UsenetClientTests.cs index 5aa4d48..c364eba 100644 --- a/tests/TorboxSDK.UnitTests/Main/Usenet/UsenetClientTests.cs +++ b/tests/TorboxSDK.UnitTests/Main/Usenet/UsenetClientTests.cs @@ -1,7 +1,7 @@ +using TorboxSDK.UnitTests.Helpers; using TorBoxSDK.Main.Usenet; using TorBoxSDK.Models.Common; using TorBoxSDK.Models.Usenet; -using TorboxSDK.UnitTests.Helpers; namespace TorboxSDK.UnitTests.Main.Usenet; @@ -187,10 +187,10 @@ public async Task RequestDownloadAsync_WithValidOptions_SendsGetRequest() { // Arrange (UsenetClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(DownloadJson); - RequestUsenetDownloadOptions options = new() { UsenetId = 1 }; + RequestUsenetDownloadOptions options = new() { }; // Act - TorBoxResponse result = await client.RequestDownloadAsync(options); + TorBoxResponse result = await client.RequestDownloadAsync(1, options); // Assert Assert.NotNull(handler.LastRequest); @@ -207,14 +207,13 @@ public async Task RequestDownloadAsync_WithAllOptions_IncludesInQueryString() (UsenetClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(DownloadJson); RequestUsenetDownloadOptions options = new() { - UsenetId = 42, Token = "custom-token", AppendName = true, Redirect = false, }; // Act - await client.RequestDownloadAsync(options); + await client.RequestDownloadAsync(42, options); // Assert Assert.NotNull(handler.LastRequest); @@ -226,13 +225,17 @@ public async Task RequestDownloadAsync_WithAllOptions_IncludesInQueryString() } [Fact] - public async Task RequestDownloadAsync_WithNullOptions_ThrowsArgumentNullException() + public async Task RequestDownloadAsync_WithNoOptions_SendsGetRequestWithIdOnly() { // Arrange - (UsenetClient client, _) = ClientTestBase.CreateClient(DownloadJson); + (UsenetClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(DownloadJson); - // Act & Assert - await Assert.ThrowsAsync(() => client.RequestDownloadAsync(null!)); + // Act + await client.RequestDownloadAsync(1); + + // Assert + Assert.NotNull(handler.LastRequest); + Assert.Contains("usenet_id=1", handler.LastRequest.RequestUri!.ToString()); } // --- GetMyUsenetListAsync --- @@ -297,10 +300,9 @@ public async Task CheckCachedAsync_WithHashes_SendsGetRequest() { // Arrange (UsenetClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(CachedJson); - IReadOnlyList hashes = ["hash1", "hash2"]; // Act - await client.CheckCachedAsync(new CheckCachedOptions { Hashes = hashes }); + await client.CheckCachedAsync(["hash1", "hash2"]); // Assert Assert.NotNull(handler.LastRequest); @@ -316,7 +318,7 @@ public async Task CheckCachedAsync_WithAllParams_IncludesInQueryString() (UsenetClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(CachedJson); // Act - await client.CheckCachedAsync(new CheckCachedOptions { Hashes = ["hash1"], Format = "object", ListFiles = true }); + await client.CheckCachedAsync(["hash1"], new CheckCachedOptions { Format = "object", ListFiles = true }); // Assert Assert.NotNull(handler.LastRequest); @@ -342,7 +344,7 @@ public async Task CheckCachedAsync_WithNullHashes_ThrowsArgumentNullException() (UsenetClient client, _) = ClientTestBase.CreateClient(CachedJson); // Act & Assert - await Assert.ThrowsAsync(() => client.CheckCachedAsync(new CheckCachedOptions { Hashes = null! })); + await Assert.ThrowsAsync(() => client.CheckCachedAsync(null!)); } // --- CheckCachedByPostAsync --- diff --git a/tests/TorboxSDK.UnitTests/Main/WebDownloads/WebDownloadsClientTests.cs b/tests/TorboxSDK.UnitTests/Main/WebDownloads/WebDownloadsClientTests.cs index 90690ba..1070a38 100644 --- a/tests/TorboxSDK.UnitTests/Main/WebDownloads/WebDownloadsClientTests.cs +++ b/tests/TorboxSDK.UnitTests/Main/WebDownloads/WebDownloadsClientTests.cs @@ -156,10 +156,10 @@ public async Task RequestDownloadAsync_WithValidOptions_SendsGetRequest() { // Arrange (WebDownloadsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(DownloadJson); - RequestWebDownloadOptions options = new() { WebId = 1 }; + RequestWebDownloadOptions options = new() { }; // Act - TorBoxResponse result = await client.RequestDownloadAsync(options); + TorBoxResponse result = await client.RequestDownloadAsync(1, options); // Assert Assert.NotNull(handler.LastRequest); @@ -176,14 +176,13 @@ public async Task RequestDownloadAsync_WithAllOptions_IncludesInQueryString() (WebDownloadsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(DownloadJson); RequestWebDownloadOptions options = new() { - WebId = 42, Token = "custom-token", AppendName = true, Redirect = false, }; // Act - await client.RequestDownloadAsync(options); + await client.RequestDownloadAsync(42, options); // Assert Assert.NotNull(handler.LastRequest); @@ -195,13 +194,17 @@ public async Task RequestDownloadAsync_WithAllOptions_IncludesInQueryString() } [Fact] - public async Task RequestDownloadAsync_WithNullOptions_ThrowsArgumentNullException() + public async Task RequestDownloadAsync_WithNoOptions_SendsGetRequestWithIdOnly() { // Arrange - (WebDownloadsClient client, _) = ClientTestBase.CreateClient(DownloadJson); + (WebDownloadsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(DownloadJson); - // Act & Assert - await Assert.ThrowsAsync(() => client.RequestDownloadAsync(null!)); + // Act + await client.RequestDownloadAsync(1); + + // Assert + Assert.NotNull(handler.LastRequest); + Assert.Contains("web_id=1", handler.LastRequest.RequestUri!.ToString()); } // --- GetMyWebDownloadListAsync --- @@ -268,7 +271,7 @@ public async Task CheckCachedAsync_WithHashes_SendsGetRequest() (WebDownloadsClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(CachedJson); // Act - await client.CheckCachedAsync(new CheckCachedOptions { Hashes = ["hash1", "hash2"] }); + await client.CheckCachedAsync(["hash1", "hash2"]); // Assert Assert.NotNull(handler.LastRequest); @@ -284,7 +287,7 @@ public async Task CheckCachedAsync_WithNullOptions_ThrowsArgumentNullException() (WebDownloadsClient client, _) = ClientTestBase.CreateClient(CachedJson); // Act & Assert - await Assert.ThrowsAsync(() => client.CheckCachedAsync(null!)); + await Assert.ThrowsAsync(() => client.CheckCachedAsync((IReadOnlyList)null!)); } [Fact] @@ -294,7 +297,7 @@ public async Task CheckCachedAsync_WithNullHashes_ThrowsArgumentNullException() (WebDownloadsClient client, _) = ClientTestBase.CreateClient(CachedJson); // Act & Assert - await Assert.ThrowsAsync(() => client.CheckCachedAsync(new CheckCachedOptions { Hashes = null! })); + await Assert.ThrowsAsync(() => client.CheckCachedAsync((IReadOnlyList)null!)); } // --- CheckCachedByPostAsync --- diff --git a/tests/TorboxSDK.UnitTests/Models/Common/CommonModelTests.cs b/tests/TorboxSDK.UnitTests/Models/Common/CommonModelTests.cs index e76eee5..0799943 100644 --- a/tests/TorboxSDK.UnitTests/Models/Common/CommonModelTests.cs +++ b/tests/TorboxSDK.UnitTests/Models/Common/CommonModelTests.cs @@ -153,7 +153,6 @@ public void CheckCachedOptions_Serialize_WithAllProperties_ProducesExpectedJson( // Arrange CheckCachedOptions options = new() { - Hashes = ["hash1", "hash2", "hash3"], Format = "object", ListFiles = true, }; @@ -165,12 +164,6 @@ public void CheckCachedOptions_Serialize_WithAllProperties_ProducesExpectedJson( using JsonDocument doc = JsonDocument.Parse(json); JsonElement root = doc.RootElement; - JsonElement hashes = root.GetProperty("hash"); - Assert.Equal(3, hashes.GetArrayLength()); - Assert.Equal("hash1", hashes[0].GetString()); - Assert.Equal("hash2", hashes[1].GetString()); - Assert.Equal("hash3", hashes[2].GetString()); - Assert.Equal("object", root.GetProperty("format").GetString()); Assert.True(root.GetProperty("list_files").GetBoolean()); } @@ -179,10 +172,7 @@ public void CheckCachedOptions_Serialize_WithAllProperties_ProducesExpectedJson( public void CheckCachedOptions_Serialize_WithNullOptionals_OmitsNullProperties() { // Arrange - CheckCachedOptions options = new() - { - Hashes = ["abc123"], - }; + CheckCachedOptions options = new(); // Act string json = JsonSerializer.Serialize(options, TorBoxJsonOptions.Default); @@ -190,7 +180,6 @@ public void CheckCachedOptions_Serialize_WithNullOptionals_OmitsNullProperties() // Assert using JsonDocument doc = JsonDocument.Parse(json); JsonElement root = doc.RootElement; - Assert.True(root.TryGetProperty("hash", out _)); Assert.False(root.TryGetProperty("format", out _)); Assert.False(root.TryGetProperty("list_files", out _)); } diff --git a/tests/TorboxSDK.UnitTests/Models/General/GeneralModelTests.cs b/tests/TorboxSDK.UnitTests/Models/General/GeneralModelTests.cs index 7a12940..cd0f114 100644 --- a/tests/TorboxSDK.UnitTests/Models/General/GeneralModelTests.cs +++ b/tests/TorboxSDK.UnitTests/Models/General/GeneralModelTests.cs @@ -153,7 +153,7 @@ public void Changelog_Deserialize_PopulatesAllProperties() // Arrange string json = """ { - "id": 7, + "id": "7", "name": "v2.5.0", "html": "

Bug fixes and improvements

", "markdown": "# Bug fixes and improvements", @@ -167,7 +167,7 @@ public void Changelog_Deserialize_PopulatesAllProperties() // Assert Assert.NotNull(result); - Assert.Equal(7, result.Id); + Assert.Equal("7", result.Id); Assert.Equal("v2.5.0", result.Name); Assert.Equal("

Bug fixes and improvements

", result.Html); Assert.Equal("# Bug fixes and improvements", result.Markdown); @@ -182,7 +182,7 @@ public void Changelog_Deserialize_WithNullOptionals_ReturnsNulls() // Arrange string json = """ { - "id": 1, + "id": "1", "name": "v1.0.0" } """; @@ -192,7 +192,7 @@ public void Changelog_Deserialize_WithNullOptionals_ReturnsNulls() // Assert Assert.NotNull(result); - Assert.Equal(1, result.Id); + Assert.Equal("1", result.Id); Assert.Equal("v1.0.0", result.Name); Assert.Null(result.Html); Assert.Null(result.Markdown); diff --git a/tests/TorboxSDK.UnitTests/Models/Notifications/NotificationModelTests.cs b/tests/TorboxSDK.UnitTests/Models/Notifications/NotificationModelTests.cs index 25f78cf..4d06c35 100644 --- a/tests/TorboxSDK.UnitTests/Models/Notifications/NotificationModelTests.cs +++ b/tests/TorboxSDK.UnitTests/Models/Notifications/NotificationModelTests.cs @@ -104,25 +104,5 @@ public void IntercomHash_Deserialize_WithNullHash_ReturnsNull() Assert.Null(result.Hash); } - // ──── GetIntercomHashOptions ──── - [Fact] - public void GetIntercomHashOptions_Serialize_ProducesExpectedJson() - { - // Arrange - GetIntercomHashOptions options = new() - { - AuthId = "auth-xyz-789", - Email = "user@example.com", - }; - - // Act - string json = JsonSerializer.Serialize(options, TorBoxJsonOptions.Default); - - // Assert - using JsonDocument doc = JsonDocument.Parse(json); - JsonElement root = doc.RootElement; - Assert.Equal("auth-xyz-789", root.GetProperty("auth_id").GetString()); - Assert.Equal("user@example.com", root.GetProperty("email").GetString()); - } } diff --git a/tests/TorboxSDK.UnitTests/Models/Relay/RelayModelTests.cs b/tests/TorboxSDK.UnitTests/Models/Relay/RelayModelTests.cs index f1df088..2383af7 100644 --- a/tests/TorboxSDK.UnitTests/Models/Relay/RelayModelTests.cs +++ b/tests/TorboxSDK.UnitTests/Models/Relay/RelayModelTests.cs @@ -141,25 +141,5 @@ public void InactiveCheckResult_Deserialize_WithNullOptionals_ReturnsNulls() Assert.Null(result.LastActive); } - // ──── CheckInactiveOptions ──── - [Fact] - public void CheckInactiveOptions_Serialize_ProducesExpectedJson() - { - // Arrange - CheckInactiveOptions options = new() - { - AuthId = "auth-relay-001", - TorrentId = 12345, - }; - - // Act - string json = JsonSerializer.Serialize(options, TorBoxJsonOptions.Default); - - // Assert - using JsonDocument doc = JsonDocument.Parse(json); - JsonElement root = doc.RootElement; - Assert.Equal("auth-relay-001", root.GetProperty("auth_id").GetString()); - Assert.Equal(12345, root.GetProperty("torrent_id").GetInt64()); - } } diff --git a/tests/TorboxSDK.UnitTests/Models/Torrents/RequestDownloadOptionsTests.cs b/tests/TorboxSDK.UnitTests/Models/Torrents/RequestDownloadOptionsTests.cs index 7073f7e..eaf8d0d 100644 --- a/tests/TorboxSDK.UnitTests/Models/Torrents/RequestDownloadOptionsTests.cs +++ b/tests/TorboxSDK.UnitTests/Models/Torrents/RequestDownloadOptionsTests.cs @@ -12,7 +12,6 @@ public void Serialize_WithAllNewProperties_ProducesExpectedJson() // Arrange RequestDownloadOptions options = new() { - TorrentId = 42, FileId = 5, ZipLink = true, UserIp = "192.168.1.1", @@ -27,7 +26,6 @@ public void Serialize_WithAllNewProperties_ProducesExpectedJson() // Assert using JsonDocument doc = JsonDocument.Parse(json); JsonElement root = doc.RootElement; - Assert.Equal(42, root.GetProperty("torrent_id").GetInt64()); Assert.Equal("my-api-token", root.GetProperty("token").GetString()); Assert.True(root.GetProperty("append_name").GetBoolean()); Assert.False(root.GetProperty("redirect").GetBoolean()); @@ -37,10 +35,7 @@ public void Serialize_WithAllNewProperties_ProducesExpectedJson() public void Serialize_WithNullNewProperties_OmitsThem() { // Arrange - RequestDownloadOptions options = new() - { - TorrentId = 1, - }; + RequestDownloadOptions options = new(); // Act string json = JsonSerializer.Serialize(options, TorBoxJsonOptions.Default); @@ -48,7 +43,6 @@ public void Serialize_WithNullNewProperties_OmitsThem() // Assert using JsonDocument doc = JsonDocument.Parse(json); JsonElement root = doc.RootElement; - Assert.Equal(1, root.GetProperty("torrent_id").GetInt64()); Assert.False(root.TryGetProperty("token", out _)); Assert.False(root.TryGetProperty("append_name", out _)); } diff --git a/tests/TorboxSDK.UnitTests/Models/Torrents/TorrentControlRequestTests.cs b/tests/TorboxSDK.UnitTests/Models/Torrents/TorrentControlRequestTests.cs index 32e145a..e2ecc35 100644 --- a/tests/TorboxSDK.UnitTests/Models/Torrents/TorrentControlRequestTests.cs +++ b/tests/TorboxSDK.UnitTests/Models/Torrents/TorrentControlRequestTests.cs @@ -121,47 +121,6 @@ public void MagnetToFileRequest_Serialize_ProducesExpectedJson() Assert.Equal("magnet:?xt=urn:btih:abc123def456ghi789", root.GetProperty("magnet").GetString()); } - // ──── ExportDataOptions ──── - - [Fact] - public void ExportDataOptions_Serialize_WithAllProperties_ProducesExpectedJson() - { - // Arrange - ExportDataOptions options = new() - { - TorrentId = 99, - ExportType = "magnet", - }; - - // Act - string json = JsonSerializer.Serialize(options, TorBoxJsonOptions.Default); - - // Assert - using JsonDocument doc = JsonDocument.Parse(json); - JsonElement root = doc.RootElement; - Assert.Equal(99, root.GetProperty("torrent_id").GetInt64()); - Assert.Equal("magnet", root.GetProperty("type").GetString()); - } - - [Fact] - public void ExportDataOptions_Serialize_WithNullType_OmitsTypeProperty() - { - // Arrange - ExportDataOptions options = new() - { - TorrentId = 50, - }; - - // Act - string json = JsonSerializer.Serialize(options, TorBoxJsonOptions.Default); - - // Assert - using JsonDocument doc = JsonDocument.Parse(json); - JsonElement root = doc.RootElement; - Assert.Equal(50, root.GetProperty("torrent_id").GetInt64()); - Assert.False(root.TryGetProperty("type", out _)); - } - // ──── GetTorrentInfoOptions ──── [Fact] @@ -170,7 +129,6 @@ public void GetTorrentInfoOptions_Serialize_WithAllProperties_ProducesExpectedJs // Arrange GetTorrentInfoOptions options = new() { - Hash = "abc123def456ghi789jkl012", Timeout = 30, UseCacheLookup = true, }; @@ -181,7 +139,6 @@ public void GetTorrentInfoOptions_Serialize_WithAllProperties_ProducesExpectedJs // Assert using JsonDocument doc = JsonDocument.Parse(json); JsonElement root = doc.RootElement; - Assert.Equal("abc123def456ghi789jkl012", root.GetProperty("hash").GetString()); Assert.Equal(30, root.GetProperty("timeout").GetInt32()); Assert.True(root.GetProperty("use_cache_lookup").GetBoolean()); } @@ -190,10 +147,7 @@ public void GetTorrentInfoOptions_Serialize_WithAllProperties_ProducesExpectedJs public void GetTorrentInfoOptions_Serialize_WithNullOptionals_OmitsNullProperties() { // Arrange - GetTorrentInfoOptions options = new() - { - Hash = "abc123", - }; + GetTorrentInfoOptions options = new(); // Act string json = JsonSerializer.Serialize(options, TorBoxJsonOptions.Default); @@ -201,7 +155,6 @@ public void GetTorrentInfoOptions_Serialize_WithNullOptionals_OmitsNullPropertie // Assert using JsonDocument doc = JsonDocument.Parse(json); JsonElement root = doc.RootElement; - Assert.Equal("abc123", root.GetProperty("hash").GetString()); Assert.False(root.TryGetProperty("timeout", out _)); Assert.False(root.TryGetProperty("use_cache_lookup", out _)); } diff --git a/tests/TorboxSDK.UnitTests/Models/Usenet/UsenetRequestTests.cs b/tests/TorboxSDK.UnitTests/Models/Usenet/UsenetRequestTests.cs index 91de886..874a17f 100644 --- a/tests/TorboxSDK.UnitTests/Models/Usenet/UsenetRequestTests.cs +++ b/tests/TorboxSDK.UnitTests/Models/Usenet/UsenetRequestTests.cs @@ -101,7 +101,6 @@ public void RequestUsenetDownloadOptions_Serialize_WithAllNewProperties_Produces // Arrange RequestUsenetDownloadOptions options = new() { - UsenetId = 42, FileId = 3, ZipLink = true, UserIp = "10.0.0.1", @@ -116,7 +115,6 @@ public void RequestUsenetDownloadOptions_Serialize_WithAllNewProperties_Produces // Assert using JsonDocument doc = JsonDocument.Parse(json); JsonElement root = doc.RootElement; - Assert.Equal(42, root.GetProperty("usenet_id").GetInt64()); Assert.Equal("usenet-token", root.GetProperty("token").GetString()); Assert.True(root.GetProperty("redirect").GetBoolean()); Assert.False(root.GetProperty("append_name").GetBoolean()); @@ -126,10 +124,7 @@ public void RequestUsenetDownloadOptions_Serialize_WithAllNewProperties_Produces public void RequestUsenetDownloadOptions_Serialize_WithNullNewProperties_OmitsThem() { // Arrange - RequestUsenetDownloadOptions options = new() - { - UsenetId = 1, - }; + RequestUsenetDownloadOptions options = new(); // Act string json = JsonSerializer.Serialize(options, TorBoxJsonOptions.Default); diff --git a/tests/TorboxSDK.UnitTests/Models/WebDownloads/WebDownloadRequestTests.cs b/tests/TorboxSDK.UnitTests/Models/WebDownloads/WebDownloadRequestTests.cs index 592fbe1..299b74f 100644 --- a/tests/TorboxSDK.UnitTests/Models/WebDownloads/WebDownloadRequestTests.cs +++ b/tests/TorboxSDK.UnitTests/Models/WebDownloads/WebDownloadRequestTests.cs @@ -100,7 +100,6 @@ public void RequestWebDownloadOptions_Serialize_WithAllNewProperties_ProducesExp // Arrange RequestWebDownloadOptions options = new() { - WebId = 42, FileId = 2, ZipLink = false, UserIp = "172.16.0.1", @@ -115,7 +114,6 @@ public void RequestWebDownloadOptions_Serialize_WithAllNewProperties_ProducesExp // Assert using JsonDocument doc = JsonDocument.Parse(json); JsonElement root = doc.RootElement; - Assert.Equal(42, root.GetProperty("web_id").GetInt64()); Assert.Equal("web-token-abc", root.GetProperty("token").GetString()); Assert.True(root.GetProperty("redirect").GetBoolean()); Assert.True(root.GetProperty("append_name").GetBoolean()); @@ -125,10 +123,7 @@ public void RequestWebDownloadOptions_Serialize_WithAllNewProperties_ProducesExp public void RequestWebDownloadOptions_Serialize_WithNullNewProperties_OmitsThem() { // Arrange - RequestWebDownloadOptions options = new() - { - WebId = 1, - }; + RequestWebDownloadOptions options = new(); // Act string json = JsonSerializer.Serialize(options, TorBoxJsonOptions.Default); diff --git a/tests/TorboxSDK.UnitTests/Relay/RelayApiClientTests.cs b/tests/TorboxSDK.UnitTests/Relay/RelayApiClientTests.cs index 74a4375..5ee98f7 100644 --- a/tests/TorboxSDK.UnitTests/Relay/RelayApiClientTests.cs +++ b/tests/TorboxSDK.UnitTests/Relay/RelayApiClientTests.cs @@ -56,7 +56,7 @@ public async Task CheckForInactiveAsync_WithValidParams_SendsCorrectUrl() (RelayApiClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(InactiveCheckJson); // Act - TorBoxResponse result = await client.CheckForInactiveAsync(new CheckInactiveOptions { AuthId = "auth-id-123", TorrentId = 42 }); + TorBoxResponse result = await client.CheckForInactiveAsync("auth-id-123", 42); // Assert Assert.NotNull(handler.LastRequest); @@ -66,13 +66,13 @@ public async Task CheckForInactiveAsync_WithValidParams_SendsCorrectUrl() } [Fact] - public async Task CheckForInactiveAsync_WithNullOptions_ThrowsArgumentNullException() + public async Task CheckForInactiveAsync_WithNullAuthId_ThrowsArgumentNullException() { // Arrange (RelayApiClient client, _) = ClientTestBase.CreateClient(InactiveCheckJson); // Act & Assert - await Assert.ThrowsAsync(() => client.CheckForInactiveAsync(null!)); + await Assert.ThrowsAsync(() => client.CheckForInactiveAsync(null!, 1)); } [Fact] @@ -82,6 +82,6 @@ public async Task CheckForInactiveAsync_WithEmptyAuthId_ThrowsArgumentException( (RelayApiClient client, _) = ClientTestBase.CreateClient(InactiveCheckJson); // Act & Assert - await Assert.ThrowsAsync(() => client.CheckForInactiveAsync(new CheckInactiveOptions { AuthId = string.Empty, TorrentId = 1 })); + await Assert.ThrowsAsync(() => client.CheckForInactiveAsync(string.Empty, 1)); } } diff --git a/tests/TorboxSDK.UnitTests/Search/SearchApiClientTests.cs b/tests/TorboxSDK.UnitTests/Search/SearchApiClientTests.cs index c5d874b..842dd6e 100644 --- a/tests/TorboxSDK.UnitTests/Search/SearchApiClientTests.cs +++ b/tests/TorboxSDK.UnitTests/Search/SearchApiClientTests.cs @@ -414,7 +414,7 @@ public async Task DownloadUsenetAsync_WithIdAndGuid_SendsGetRequest() (SearchApiClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(downloadJson); // Act - TorBoxResponse result = await client.DownloadUsenetAsync(new DownloadUsenetOptions { Id = "nzb-id-123", Guid = "guid-456" }); + TorBoxResponse result = await client.DownloadUsenetAsync("nzb-id-123", "guid-456"); // Assert Assert.NotNull(handler.LastRequest); @@ -424,13 +424,13 @@ public async Task DownloadUsenetAsync_WithIdAndGuid_SendsGetRequest() } [Fact] - public async Task DownloadUsenetAsync_WithNullOptions_ThrowsArgumentNullException() + public async Task DownloadUsenetAsync_WithNullId_ThrowsArgumentNullException() { // Arrange (SearchApiClient client, _) = ClientTestBase.CreateClient(SearchResultsJson); // Act & Assert - await Assert.ThrowsAsync(() => client.DownloadUsenetAsync(null!)); + await Assert.ThrowsAsync(() => client.DownloadUsenetAsync(null!, "guid")); } [Fact] @@ -440,7 +440,7 @@ public async Task DownloadUsenetAsync_WithEmptyId_ThrowsArgumentException() (SearchApiClient client, _) = ClientTestBase.CreateClient(SearchResultsJson); // Act & Assert - await Assert.ThrowsAsync(() => client.DownloadUsenetAsync(new DownloadUsenetOptions { Id = "", Guid = "guid" })); + await Assert.ThrowsAsync(() => client.DownloadUsenetAsync("", "guid")); } [Fact] @@ -450,7 +450,7 @@ public async Task DownloadUsenetAsync_WithEmptyGuid_ThrowsArgumentException() (SearchApiClient client, _) = ClientTestBase.CreateClient(SearchResultsJson); // Act & Assert - await Assert.ThrowsAsync(() => client.DownloadUsenetAsync(new DownloadUsenetOptions { Id = "id", Guid = "" })); + await Assert.ThrowsAsync(() => client.DownloadUsenetAsync("id", "")); } // --- GetMetaByIdAsync --- @@ -518,7 +518,7 @@ public async Task SearchTorznabAsync_WithQuery_SendsGetRequest() (SearchApiClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(torznabJson); // Act - TorBoxResponse result = await client.SearchTorznabAsync(new SearchTorznabOptions { Query = "ubuntu" }); + TorBoxResponse result = await client.SearchTorznabAsync("ubuntu"); // Assert Assert.NotNull(handler.LastRequest); @@ -545,7 +545,7 @@ public async Task SearchTorznabAsync_WithApiKey_IncludesInQueryString() (SearchApiClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(torznabJson); // Act - await client.SearchTorznabAsync(new SearchTorznabOptions { Query = "ubuntu", ApiKey = "my-key" }); + await client.SearchTorznabAsync("ubuntu", "my-key"); // Assert Assert.NotNull(handler.LastRequest); @@ -554,7 +554,7 @@ public async Task SearchTorznabAsync_WithApiKey_IncludesInQueryString() } [Fact] - public async Task SearchTorznabAsync_WithNullOptions_ThrowsArgumentNullException() + public async Task SearchTorznabAsync_WithNullQuery_ThrowsArgumentNullException() { // Arrange (SearchApiClient client, _) = ClientTestBase.CreateClient(SearchResultsJson); @@ -570,7 +570,7 @@ public async Task SearchTorznabAsync_WithEmptyQuery_ThrowsArgumentException() (SearchApiClient client, _) = ClientTestBase.CreateClient(SearchResultsJson); // Act & Assert - await Assert.ThrowsAsync(() => client.SearchTorznabAsync(new SearchTorznabOptions { Query = "" })); + await Assert.ThrowsAsync(() => client.SearchTorznabAsync("")); } // --- SearchNewznabAsync --- @@ -590,7 +590,7 @@ public async Task SearchNewznabAsync_WithQuery_SendsGetRequest() (SearchApiClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(newznabJson); // Act - TorBoxResponse result = await client.SearchNewznabAsync(new SearchNewznabOptions { Query = "ubuntu" }); + TorBoxResponse result = await client.SearchNewznabAsync("ubuntu"); // Assert Assert.NotNull(handler.LastRequest); @@ -603,7 +603,7 @@ public async Task SearchNewznabAsync_WithQuery_SendsGetRequest() } [Fact] - public async Task SearchNewznabAsync_WithNullOptions_ThrowsArgumentNullException() + public async Task SearchNewznabAsync_WithNullQuery_ThrowsArgumentNullException() { // Arrange (SearchApiClient client, _) = ClientTestBase.CreateClient(SearchResultsJson); @@ -619,7 +619,7 @@ public async Task SearchNewznabAsync_WithEmptyQuery_ThrowsArgumentException() (SearchApiClient client, _) = ClientTestBase.CreateClient(SearchResultsJson); // Act & Assert - await Assert.ThrowsAsync(() => client.SearchNewznabAsync(new SearchNewznabOptions { Query = "" })); + await Assert.ThrowsAsync(() => client.SearchNewznabAsync("")); } [Fact] @@ -637,7 +637,7 @@ public async Task SearchNewznabAsync_WithApiKey_IncludesInQueryString() (SearchApiClient client, MockHttpMessageHandler handler) = ClientTestBase.CreateClient(newznabJson); // Act - await client.SearchNewznabAsync(new SearchNewznabOptions { Query = "ubuntu", ApiKey = "my-key" }); + await client.SearchNewznabAsync("ubuntu", "my-key"); // Assert Assert.NotNull(handler.LastRequest);