Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe pull request updates Docker image construction, removes the HTTP ChangesContainer and HTTP updates
Application version metadata
Radarr availability configuration
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Entrypoint
participant ConfigurationUtils
participant RadarrUtils
participant RadarrAPI
Entrypoint->>ConfigurationUtils: Pass radarr.availability
ConfigurationUtils->>RadarrUtils: Provide optional radarrAvailability
RadarrUtils->>RadarrUtils: Build RadarrPost with minimumAvailability
RadarrUtils->>RadarrAPI: Log and send serialized movie payload
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/main/scala/http/HttpClient.scala`:
- Around line 23-26: The current FollowRedirect usage sets sensitiveHeaderFilter
= _ => true which forwards all sensitive headers to every redirect; update the
filter used in FollowRedirect (the call that constructs
FollowRedirect[IO](maxRedirects = 10, sensitiveHeaderFilter = ...)(c)) to only
allow sensitive headers for same-origin redirects or a trusted-host whitelist:
implement a predicate that compares the original request URI host (and
scheme/port if needed) against the redirect target host and returns true only
when they match (or when the redirect host is in a configured trustedDomains
set), and pass that predicate as sensitiveHeaderFilter so
Authorization/Cookie/X-Api-Key/X-Plex-Token are not sent to cross-origin
redirects.
Remove Host header which is rejected when redirect followed. Instead, let http4s set the Host header
|
Is the project abandoned ? |
|
@nylonee, can't we merge this to get the application working again? |
|
Please merge the PR! |
Adds RADARR_AVAILABILITY env var to control movie availability in Radarr. When set (e.g. RADARR_AVAILABILITY=announced), the minimumAvailability field is included in the POST payload to /api/v3/movie. Omitted when not set to avoid null serialization. Also adds startup version log.
…alues.
Line 191 accepts raw Option[String] without normalization. If the runtime injects an empty property value (e.g., -Dradarr.availability=), this becomes Some(""). This value propagates through to RadarrPost.scala (line 24-26), where it serializes as "minimumAvailability": "" in the JSON payload, which the Radarr API does not accept.
|
We need to get this approved |
| RUN chmod a+rx /app/target/universal/stage/bin/* | ||
|
|
||
| FROM openjdk:11-jre-slim | ||
| FROM eclipse-temurin:11-jre-jammy |
There was a problem hiding this comment.
Was this necessary to get the image to build? If not removing these changes would make the change smaller and easier to get merged (although it doesn't appear like the size of the change is the issue and this is abandoned)
There was a problem hiding this comment.
Yes, was necessary as openjdk has been deprecated.
Add radarr availability
There was a problem hiding this comment.
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 `@src/main/scala/configuration/ConfigurationUtils.scala`:
- Around line 191-192: Update the Radarr configuration loading flow around the
availability value in ConfigurationUtils so, after trimming and ignoring empty
values, only tba, announced, inCinemas, released, and deleted are accepted.
Reject unsupported non-empty values during startup with a clear configuration
error, while preserving the existing optional behavior for absent or blank
availability.
In `@src/main/scala/radarr/RadarrPost.scala`:
- Around line 15-29: Update the RadarrPost encoder to include an addOptions
object containing searchForMovie set to true in the serialized movie payload.
Preserve the existing fields and optional minimumAvailability handling while
ensuring every added movie requests an immediate search.
🪄 Autofix
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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 73c0bdee-af1d-4a1d-aa4d-eb1f9ed4b0a4
📒 Files selected for processing (9)
build.sbtdocker/entrypoint.shsrc/main/scala/Server.scalasrc/main/scala/configuration/Configuration.scalasrc/main/scala/configuration/ConfigurationRedactor.scalasrc/main/scala/configuration/ConfigurationUtils.scalasrc/main/scala/configuration/Keys.scalasrc/main/scala/radarr/RadarrPost.scalasrc/main/scala/radarr/RadarrUtils.scala
| availability = configReader.getConfigOption(Keys.radarrAvailability).map(_.trim).filter(_.nonEmpty) | ||
| } yield (url, apiKey, qualityProfileId, rootFolder, tagIds, availability) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
# Show the supported Radarr values.
curl -fsSL \
'https://raw.githubusercontent.com/Radarr/Radarr/develop/src/Radarr.Api.V3/openapi.json' |
jq -r '.components.schemas.MovieStatusType.enum[]'
# Trace validation and error-reporting paths.
rg -n -C4 --glob '*.scala' \
'radarrAvailability|minimumAvailability|Sent .* to Radarr|Received warning for sending' .Repository: nylonee/watchlistarr
Length of output: 9455
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
# Inspect imports/dependencies and exact call sites relevant to validation and HTTP response handling.
rg -n --glob '*.scala' 'case class RadarrConfiguration|def postToArr|EitherT|Client|httpClient|raiseFailures' .
git ls-files '*build*' | sed -n '1,120p'
cat -n src/main/scala/configuration/ConfigurationUtils.scala | sed -n '160,200p'
cat -n src/main/scala/configuration/Configuration.scala | sed -n '1,80p'
if [ -f src/main/scala/radarr/RadarrUtils.scala ]; then
cat -n src/main/scala/radarr/RadarrUtils.scala | sed -n '1,70p'
fi
if [ -f src/main/scala/radarr/RadarrPost.scala ]; then
cat -n src/main/scala/radarr/RadarrPost.scala | sed -n '1,40p'
fi
# Check whether the repository has any existing enum-like helper for Radarr availability.
rg -n --glob '*.scala' 'tba|announced|inCinemas|released|deleted|MovieStatusType|RadarrAvailability|radarrAvailability' .Repository: nylonee/watchlistarr
Length of output: 35372
Reject unsupported Radarr availability values during configuration loading.
radarr.availability currently accepts any non-empty string and sends it as minimumAvailability. Radarr only supports tba, announced, inCinemas, released, and deleted. A typo or unsupported value will silently be logged as a warning, then reported as successfully sent. Validate this field before startup and fail with a clear error for invalid values.
🤖 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 `@src/main/scala/configuration/ConfigurationUtils.scala` around lines 191 -
192, Update the Radarr configuration loading flow around the availability value
in ConfigurationUtils so, after trimming and ignoring empty values, only tba,
announced, inCinemas, released, and deleted are accepted. Reject unsupported
non-empty values during startup with a clear configuration error, while
preserving the existing optional behavior for absent or blank availability.
| object RadarrPost { | ||
| implicit val encoder: Encoder[RadarrPost] = Encoder.instance { post => | ||
| var fields = List[(String, Json)]( | ||
| "title" -> post.title.asJson, | ||
| "tmdbId" -> post.tmdbId.asJson, | ||
| "qualityProfileId" -> post.qualityProfileId.asJson, | ||
| "rootFolderPath" -> post.rootFolderPath.asJson, | ||
| "tags" -> post.tags.asJson | ||
| ) | ||
| post.minimumAvailability.foreach { avail => | ||
| fields = fields :+ ("minimumAvailability" -> avail.asJson) | ||
| } | ||
| Json.fromJsonObject(JsonObject.fromIterable(fields)) | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -n -C4 --glob '*.scala' \
'AddOptions|addOptions|searchForMovie|RadarrPost' .
curl -fsSL \
'https://raw.githubusercontent.com/Radarr/Radarr/develop/src/Radarr.Api.V3/openapi.json' |
jq -e '.components.schemas.AddMovieOptions.properties.searchForMovie'Repository: nylonee/watchlistarr
Length of output: 7623
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- RadarrPost.scala ---'
cat -n src/main/scala/radarr/RadarrPost.scala
printf '%s\n' '--- RadarrUtils.scala relevant section ---'
cat -n src/main/scala/radarr/RadarrUtils.scala
printf '%s\n' '--- Radarr delete post addOptions (context) ---'
cat -n src/main/scala/radarr/RadarrDelete.scala
printf '%s\n' '--- json output from current encoder ---'
python3 - <<'PY'
from pathlib import Path
src = Path('src/main/scala/radarr/RadarrPost.scala').read_text()
print('addOptions field in RadarrPost.scala:', 'addOptions' in src)
print('searchForMovie in RadarrPost.scala:', 'searchForMovie' in src)
PYRepository: nylonee/watchlistarr
Length of output: 6063
🌐 Web query:
Radarr API add movie addOptions searchForMovie default true
💡 Result:
To trigger an automatic search when adding a movie via the Radarr API, you must include the addOptions object in your POST request body with the searchForMovie property set to true [1][2][3]. The correct JSON structure for the request body is as follows: { "title": "Movie Title", "tmdbId": 12345, "qualityProfileId": 1, "rootFolderPath": "/path/to/movies", "monitored": true, "addOptions": { "searchForMovie": true } } When adding a movie via the POST /api/v3/movie endpoint, the searchForMovie option is not set to true by default [1]. Including this option explicitly in the payload instructs Radarr to perform an immediate search for the movie upon addition [1][2]. This mechanism is widely used to replicate the behavior available in the Radarr Web UI where users can check an option to automatically search for the movie once it is added [1].
Citations:
- 1: API endpoint for Add+Search? Radarr/Radarr#1591
- 2: https://wiki.indie-it.com/wiki/Radarr
- 3: https://www.reddit.com/r/radarr/comments/a4io1p/api_added_movies_are_not_automatically_search/
Set searchForMovie explicitly when adding movies to Radarr.
RadarrPost no longer sends addOptions, so Radarr will not perform the automatic search after POST /api/v3/movie. Add addOptions: { "searchForMovie": true } unless the target integration is not intended to search immediately after adding a movie.
🤖 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 `@src/main/scala/radarr/RadarrPost.scala` around lines 15 - 29, Update the
RadarrPost encoder to include an addOptions object containing searchForMovie set
to true in the serialized movie payload. Preserve the existing fields and
optional minimumAvailability handling while ensuring every added movie requests
an immediate search.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
There was a problem hiding this comment.
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 `@src/main/scala/sonarr/SonarrUtils.scala`:
- Line 95: Update the Sonarr request logging around the logger.info call to
avoid emitting the full payload at INFO level; log only the endpoint and request
status there, and move any detailed payload diagnostics to DEBUG using a
redacted payload.
- Line 95: Move the Sonarr request log in postToArr into the IO effect that
performs the request, ensuring it executes only when the request is run rather
than while the EitherT is built. Preserve the existing request behavior and
avoid logging the payload during lazy construction, failure, or cancellation.
🪄 Autofix
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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 6ff05e4b-e5d2-401b-8c9c-823247c2fcd2
📒 Files selected for processing (2)
src/main/scala/radarr/RadarrUtils.scalasrc/main/scala/sonarr/SonarrUtils.scala
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/scala/radarr/RadarrUtils.scala
| client: HttpClient | ||
| )(baseUrl: Uri, apiKey: String, endpoint: String)(payload: Json): EitherT[IO, Throwable, T] = | ||
| )(baseUrl: Uri, apiKey: String, endpoint: String)(payload: Json): EitherT[IO, Throwable, T] = { | ||
| logger.info(s"Sending JSON to Sonarr: $payload") |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Do not log the full payload at INFO level.
Line [95] writes the complete Sonarr JSON payload to application logs. The payload includes media metadata and configuration values such as titles, identifiers, root folders, profile IDs, and tags. Retained or shared logs can expose user library information and filesystem paths.
Log only the endpoint and request status at INFO level. Use a redacted payload at DEBUG level only when detailed diagnostics are required.
Proposed fix
- logger.info(s"Sending JSON to Sonarr: $payload")
+ logger.info(s"Sending request to Sonarr endpoint: $endpoint")📝 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.
| logger.info(s"Sending JSON to Sonarr: $payload") | |
| logger.info(s"Sending request to Sonarr endpoint: $endpoint") |
🤖 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 `@src/main/scala/sonarr/SonarrUtils.scala` at line 95, Update the Sonarr
request logging around the logger.info call to avoid emitting the full payload
at INFO level; log only the endpoint and request status there, and move any
detailed payload diagnostics to DEBUG using a redacted payload.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify that the Sonarr log is sequenced inside the returned effect.
rg -n -C 8 'logger\.info\(s"Sending JSON to Sonarr|def postToArr|client\.httpRequest' src/main/scalaRepository: nylonee/watchlistarr
Length of output: 17884
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== SonarrUtils context =="
sed -n '1,140p' src/main/scala/sonarr/SonarrUtils.scala
echo
echo "== postToArr call sites =="
rg -n -C 4 'postToArr\b|EitherT\(client\.httpRequest' src/main/scala/sonarr src/main/scala/radarr src/main/scala
echo
echo "== Cats IO/EitherT availability in project files =="
rg -n 'cats-effect|typelevel|cats' build.sbt project . --glob '!target/**' --glob '!node_modules/**' 2>/dev/null || trueRepository: nylonee/watchlistarr
Length of output: 21767
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Use an embedded REPL-free behavioral probe that treats source text as data.
# It checks whether the INFO log is syntactically outside the IO/EitherT effect body.
python3 - <<'PY'
from pathlib import Path
import re
path = Path('src/main/scala/sonarr/SonarrUtils.scala')
src = path.read_text()
def extract_function_method(method_name: str):
# Find the declaration and then consume balanced braces, with support for string interpolation chars.
# For demonstration, the SonarrUtils method is simple enough that string literal balance is sufficient.
pattern = re.compile(f'\\bdef\\s+{re.escape(method_name)}\\b\\s*\\([^)]*\\)\\s*[^=]*=\\s*\\n')
m = pattern.search(src)
if not m:
return None
start = m.start()
i = src.find('{', m.end())
if i == -1:
return None
depth = 0
squote = dquote = raw = rawd = False
while i < len(src):
c = src[i]
cc = src[i:i+2]
if raw:
if c == '`': raw = False
elif rawd:
if src.startswith('"""', i): rawd = False; i += 2; continue
elif cc == '`'': raw = True; i += 1; continue
elif cc == '"""': rawd = True; continue
elif c == '"' and not dquote:
dquote = True
elif c == '"' and dquote:
dquote = False
elif cc == '{{': i += 1; continue
elif cc == '}}': i += 1; continue
else:
if c == '{': depth += 1
elif c == '}':
depth -= 1
if depth == 0:
end = i + 1
break
i += 1
return src[start:end], start, end
body, start, end = extract_function_method('postToArr')
print(f'function_offset_start={start}')
print(f'log_offset={src.find("logger.info(s\"Sending JSON to Sonarr:", start, end)}')
print(f'for_offset={src.find("for {", start, end)}')
inside_brace = body.index('logger.info(s"Sending JSON to Sonarr:') < body.index('for {')
# The method assigns into a for-comprehension; the returned effect starts at the for, not before it.
returned_effect_start = body.index('for {')
print(f'log_before_for={inside_brace}')
print(f'log_inside_returned_effect_body={inside_brace}')
print(f'postToArr_return_effect_starts_at_for={returned_effect_start > start}')
PYRepository: nylonee/watchlistarr
Length of output: 332
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Exact Sonarr log position =="
python3 - <<'PY'
from pathlib import Path
text = Path('src/main/scala/sonarr/SonarrUtils.scala').read_text()
needle = 'logger.info(s"Sending JSON to Sonarr:'
start = text.find(needle, text.index('private def postToArr'))
end = text.index('\n}', start) + 2
lines = text.splitlines()
for_offset = text.find('for {', text.indexOf('private def postToArr') if hasattr(text,'indexOf') else text.index('private def postToArr'))
if start != -1:
line_no = text[:text.find(needle, text.index('private def postToArr'))].count('\n') + 1
print(f'log_at_line={line_no}')
print(f'for_at_block={text.find("for {", start)}')
PY
echo
echo "== Read-only line-number sanity check from git diff against local file =="
(git diff -- src/main/scala/sonarr/SonarrUtils.scala | sed -n '1,80p' || true)
sed -n '92,103p' src/main/scala/sonarr/SonarrUtils.scalaRepository: nylonee/watchlistarr
Length of output: 869
Move the Sonarr request log inside the IO effect.
Line 95 logs while postToArr builds the EitherT, before the request effect runs. A lazy failed or canceled IO can still log Sending JSON to Sonarr:... and expose the full payload in retained logs.
Proposed fix
)(baseUrl: Uri, apiKey: String, endpoint: String)(payload: Json): EitherT[IO, Throwable, T] = {
- logger.info(s"Sending JSON to Sonarr: $payload")
for {
+ _ <- EitherT.liftF[IO, Throwable, Unit](IO(logger.info(s"Sending JSON to Sonarr: $payload")))
response <- EitherT(
client.httpRequest(Method.POST, baseUrl / "api" / "v3" / endpoint, Some(apiKey), Some(payload))
)📝 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.
| logger.info(s"Sending JSON to Sonarr: $payload") | |
| )(baseUrl: Uri, apiKey: String, endpoint: String)(payload: Json): EitherT[IO, Throwable, T] = { | |
| for { | |
| _ <- EitherT.liftF[IO, Throwable, Unit](IO(logger.info(s"Sending JSON to Sonarr: $payload"))) | |
| response <- EitherT( | |
| client.httpRequest(Method.POST, baseUrl / "api" / "v3" / endpoint, Some(apiKey), Some(payload)) | |
| ) |
🤖 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 `@src/main/scala/sonarr/SonarrUtils.scala` at line 95, Move the Sonarr request
log in postToArr into the IO effect that performs the request, ensuring it
executes only when the request is run rather than while the EitherT is built.
Preserve the existing request behavior and avoid logging the payload during lazy
construction, failure, or cancellation.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Description
Fix 403 forbidden error when following redirect.
Remove Host header which is rejected when redirect followed. Instead, let http4s set the Host header
Checklist
sbt scalafmtAllRun (and optionallysbt scalafmtSbt)Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Chores