Skip to content

Get Jellyfin Sink working with TVDB#1767

Merged
IgnisDa merged 8 commits into
mainfrom
IgnisDa/issue1766
May 17, 2026
Merged

Get Jellyfin Sink working with TVDB#1767
IgnisDa merged 8 commits into
mainfrom
IgnisDa/issue1766

Conversation

@IgnisDa
Copy link
Copy Markdown
Owner

@IgnisDa IgnisDa commented May 16, 2026

Implements functionality to integrate Jellyfin Sink with TVDB by introducing a new json_response function for handling API responses. This enhancement improves error logging and response handling across various TVDB service calls.

Fixes #1766

Summary by CodeRabbit

  • Bug Fixes

    • Fixed TVDB identifier resolution priority in Jellyfin integrations for improved metadata matching accuracy.
  • Refactor

    • Enhanced API response handling with improved logging and security measures for credential protection.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 36982179-81df-44f5-9aea-9cdf6d7240dc

📥 Commits

Reviewing files that changed from the base of the PR and between 40b2d95 and 7a17584.

📒 Files selected for processing (2)
  • crates/providers/tvdb/src/base.rs
  • crates/services/integration/src/sink/jellyfin.rs

Walkthrough

The PR refactors JSON response parsing in IGDB by introducing logging and token-redaction helpers, applies them across all IGDB API call sites, refactors TVDB translation deserialization, and adjusts Jellyfin's TVDB identifier precedence to prefer series over item.

Changes

IGDB JSON response parsing helpers

Layer / File(s) Summary
Define json_response helpers with logging
crates/providers/igdb/src/lib.rs
Add ryot_log import and define generic json_response<T> that logs raw response bodies, plus json_response_redacted<T> variant that redacts access_token before logging, then deserialize to the requested type.
Apply helpers across IGDB call sites
crates/providers/igdb/src/lib.rs
Replace direct .json().await? parsing with helper functions across collection search/details, people/company search and details, game details with time-to-beat, metadata search, pagination, and auth token exchange; adjust collection details mapping to use the popped item's fields.

TVDB integration improvements

Layer / File(s) Summary
Refactor TVDB translation response deserialization
crates/providers/tvdb/src/base.rs
Switch translate from chained generic .json::<Type>().await? to explicit typed assignment let response: TvdbItemTranslationResponse = response.json().await?.
Adjust Jellyfin sink TVDB id lookup precedence
crates/services/integration/src/sink/jellyfin.rs
When use_tvdb is enabled in sink_progress, prefer payload.series.provider_ids.tvdb and only fall back to payload.item.provider_ids.tvdb if the series TVDB ID is absent.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • IgnisDa/ryot#1764: Modifies the same sink_progress TVDB identifier extraction logic in Jellyfin sink.
  • IgnisDa/ryot#1761: Refactors TVDB API response handling and deserialization logic in the same provider area.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Get Jellyfin Sink working with TVDB' accurately summarizes the main objective of the PR, which involves integrating Jellyfin Sink with TVDB and fixing related API response handling.
Linked Issues check ✅ Passed The PR addresses issue #1766 by implementing functionality to integrate Jellyfin Sink with TVDB, including JSON response parsing helpers and corrected TVDB identifier lookup logic in the sink_progress function.
Out of Scope Changes check ✅ Passed All changes are directly related to enabling Jellyfin Sink integration with TVDB: JSON response helpers in IGDB library (supporting TVDB response handling), TVDB base response parsing improvements, and Jellyfin sink TVDB ID lookup correction.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch IgnisDa/issue1766

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/providers/tvdb/src/base.rs`:
- Around line 28-44: json_response currently deserializes and logs the entire
response body even on non-2xx responses; change json_response to first check
response.status() and if !status.is_success() read the body bytes and return a
clear error containing the HTTP status and a bounded preview (e.g., first 256
bytes) plus total byte length instead of attempting serde deserialization, and
only for successful statuses read the body, log a truncated preview (limit to N
bytes) with the byte length, then deserialize with serde_json::from_slice; refer
to json_response, response, status, body and ensure errors propagate as a
meaningful HTTP error when not success.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1e0f6b5f-5814-49b4-bb5a-9b5e7adfb80d

📥 Commits

Reviewing files that changed from the base of the PR and between ef0d999 and f42f8db.

📒 Files selected for processing (4)
  • crates/providers/tvdb/src/base.rs
  • crates/providers/tvdb/src/movies.rs
  • crates/providers/tvdb/src/non_metadata.rs
  • crates/providers/tvdb/src/shows.rs

Comment thread crates/providers/tvdb/src/base.rs Outdated
Comment on lines +28 to +44
pub(crate) async fn json_response<T>(response: reqwest::Response, label: &str) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let status = response.status();
let body = response.bytes().await?;

ryot_log!(
debug,
"TVDB {} response body ({}): {}",
label,
status,
String::from_utf8_lossy(&body)
);

Ok(serde_json::from_slice(&body)?)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle non-2xx responses explicitly and avoid full-body debug dumps.

json_response currently attempts deserialization even for HTTP errors and logs entire payloads. That can turn API failures into opaque serde errors and create very large logs for extended series/season responses. Gate on status and log only a bounded preview plus byte size.

Suggested patch
 pub(crate) async fn json_response<T>(response: reqwest::Response, label: &str) -> Result<T>
 where
     T: serde::de::DeserializeOwned,
 {
     let status = response.status();
     let body = response.bytes().await?;
+    let preview = String::from_utf8_lossy(&body);
+    let preview = preview.chars().take(1024).collect::<String>();

     ryot_log!(
         debug,
-        "TVDB {} response body ({}): {}",
+        "TVDB {} response (status={}, bytes={}, preview={}...)",
         label,
         status,
-        String::from_utf8_lossy(&body)
+        body.len(),
+        preview
     );
 
+    if !status.is_success() {
+        bail!(
+            "TVDB {} request failed (status={}, body_preview={}...)",
+            label,
+            status,
+            preview
+        );
+    }
+
     Ok(serde_json::from_slice(&body)?)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub(crate) async fn json_response<T>(response: reqwest::Response, label: &str) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let status = response.status();
let body = response.bytes().await?;
ryot_log!(
debug,
"TVDB {} response body ({}): {}",
label,
status,
String::from_utf8_lossy(&body)
);
Ok(serde_json::from_slice(&body)?)
}
pub(crate) async fn json_response<T>(response: reqwest::Response, label: &str) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
let status = response.status();
let body = response.bytes().await?;
let preview = String::from_utf8_lossy(&body);
let preview = preview.chars().take(1024).collect::<String>();
ryot_log!(
debug,
"TVDB {} response (status={}, bytes={}, preview={}...)",
label,
status,
body.len(),
preview
);
if !status.is_success() {
bail!(
"TVDB {} request failed (status={}, body_preview={}...)",
label,
status,
preview
);
}
Ok(serde_json::from_slice(&body)?)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/providers/tvdb/src/base.rs` around lines 28 - 44, json_response
currently deserializes and logs the entire response body even on non-2xx
responses; change json_response to first check response.status() and if
!status.is_success() read the body bytes and return a clear error containing the
HTTP status and a bounded preview (e.g., first 256 bytes) plus total byte length
instead of attempting serde deserialization, and only for successful statuses
read the body, log a truncated preview (limit to N bytes) with the byte length,
then deserialize with serde_json::from_slice; refer to json_response, response,
status, body and ensure errors propagate as a meaningful HTTP error when not
success.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/providers/igdb/src/lib.rs`:
- Around line 237-263: The json_response_redacted function currently returns
Ok(serde_json::from_slice(&body)?) without context; update the final
deserialization to include error context like json_response does by using
serde_json::from_slice(&body) with a Context/with_context call (or map_err) that
attaches the label and the response body (String::from_utf8_lossy(&body)) to the
error message so failures in json_response_redacted produce the same contextual
error as json_response; apply this change to the final return in
json_response_redacted.
- Around line 219-235: The json_response function currently returns
serde_json::from_slice(&body)? which loses endpoint context on deserialization
errors; modify json_response (function name json_response) to map or wrap the
serde_json::from_slice error with context that includes the label (and
optionally status and a truncated body) using anyhow::Context or map_err to
build an anyhow error so failures indicate which IGDB endpoint/label failed
during deserialization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c1999ad4-3fef-45b8-bac5-88c27b4056a1

📥 Commits

Reviewing files that changed from the base of the PR and between f42f8db and 40b2d95.

📒 Files selected for processing (2)
  • crates/providers/igdb/src/lib.rs
  • crates/services/integration/src/sink/jellyfin.rs

Comment thread crates/providers/igdb/src/lib.rs Outdated
Comment on lines +219 to +235
async fn json_response<T>(response: Response, label: &str) -> Result<T>
where
T: DeserializeOwned,
{
let status = response.status();
let body = response.bytes().await?;

ryot_log!(
debug,
"IGDB {} response body ({}): {}",
label,
status,
String::from_utf8_lossy(&body)
);

Ok(serde_json::from_slice(&body)?)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add context to deserialization errors for easier debugging.

When from_slice fails, the error loses the label context. Wrapping with anyhow! would preserve which endpoint failed.

♻️ Proposed fix
 async fn json_response<T>(response: Response, label: &str) -> Result<T>
 where
     T: DeserializeOwned,
 {
     let status = response.status();
     let body = response.bytes().await?;
 
     ryot_log!(
         debug,
         "IGDB {} response body ({}): {}",
         label,
         status,
         String::from_utf8_lossy(&body)
     );
 
-    Ok(serde_json::from_slice(&body)?)
+    serde_json::from_slice(&body)
+        .map_err(|e| anyhow!("Failed to parse IGDB '{}' response ({}): {}", label, status, e))
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async fn json_response<T>(response: Response, label: &str) -> Result<T>
where
T: DeserializeOwned,
{
let status = response.status();
let body = response.bytes().await?;
ryot_log!(
debug,
"IGDB {} response body ({}): {}",
label,
status,
String::from_utf8_lossy(&body)
);
Ok(serde_json::from_slice(&body)?)
}
async fn json_response<T>(response: Response, label: &str) -> Result<T>
where
T: DeserializeOwned,
{
let status = response.status();
let body = response.bytes().await?;
ryot_log!(
debug,
"IGDB {} response body ({}): {}",
label,
status,
String::from_utf8_lossy(&body)
);
serde_json::from_slice(&body)
.map_err(|e| anyhow!("Failed to parse IGDB '{}' response ({}): {}", label, status, e))
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/providers/igdb/src/lib.rs` around lines 219 - 235, The json_response
function currently returns serde_json::from_slice(&body)? which loses endpoint
context on deserialization errors; modify json_response (function name
json_response) to map or wrap the serde_json::from_slice error with context that
includes the label (and optionally status and a truncated body) using
anyhow::Context or map_err to build an anyhow error so failures indicate which
IGDB endpoint/label failed during deserialization.

Comment thread crates/providers/igdb/src/lib.rs Outdated
@IgnisDa IgnisDa merged commit ae9d02d into main May 17, 2026
13 checks passed
@IgnisDa IgnisDa deleted the IgnisDa/issue1766 branch May 17, 2026 03:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Get Jellyfin Sink working with TVDB

1 participant