diff --git a/.gitignore b/.gitignore index 763df82..f0ede70 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ compose.yaml /target data/ .vscode +.dev-env diff --git a/Cargo.lock b/Cargo.lock index bab0d42..55da572 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1054,7 +1054,7 @@ dependencies = [ [[package]] name = "seadexerr" -version = "0.4.0" +version = "0.4.1" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml index ea50efd..96ebd54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,28 @@ [package] name = "seadexerr" -version = "0.4.0" +version = "0.4.1" edition = "2024" [dependencies] anyhow = "1.0.100" axum = { version = "0.8.7", features = ["macros"] } quick-xml = "0.38.4" -reqwest = { version = "0.12.24", default-features = false, features = ["json", "rustls-tls"] } +reqwest = { version = "0.12.24", default-features = false, features = [ + "json", + "rustls-tls", +] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.145" serde_with = "3.16.0" thiserror = "2.0.17" time = { version = "0.3.44", features = ["formatting", "parsing"] } -tokio = { version = "1.48.0", features = ["fs", "macros", "rt-multi-thread", "sync", "time"] } +tokio = { version = "1.48.0", features = [ + "fs", + "macros", + "rt-multi-thread", + "sync", + "time", +] } tracing = "0.1.41" tracing-subscriber = { version = "0.3.20", features = ["fmt", "env-filter"] } url = "2.5.7" diff --git a/README.md b/README.md index 6d34da9..fb01f0b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A Prowlarr indexer for [Seadex](https://releases.moe/) torrents. Always get the best Seadex release. > [!NOTE] -> Requires indexer flag `internal` to be unused for now +> Automatic Searching requires indexer flag `internal` to be unused for now ## Docker Compose @@ -77,6 +77,7 @@ In Sonarr or Radarr: - [x] Movie Support (TMDB + Radarr) - [x] RSS Refresh - [x] Local PlexAniBridge Mappings +- [x] Season Pack Support This project uses [PlexAniBridge Mappings](https://github.com/eliasbenb/PlexAniBridge-Mappings). diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..128df5b --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1763966396, + "narHash": "sha256-6eeL1YPcY1MV3DDStIDIdy/zZCDKgHdkCmsrLJFiZf0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5ae3b07d8d6527c42f17c876e404993199144b6a", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..cc8082c --- /dev/null +++ b/flake.nix @@ -0,0 +1,128 @@ +{ + description = "Seadexerr development environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { + inherit system; + config.allowUnfree = true; + }; + in + { + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + sonarr + radarr + prowlarr + + # Rust development tools + cargo + rustc + rustfmt + clippy + + # Common dependencies + pkg-config + openssl + ]; + + shellHook = '' + echo "Seadexerr development environment loaded." + + export RUST_LOG="debug" + + export SEADEXERR_DEV_ROOT="$PWD/.dev-env" + mkdir -p "$SEADEXERR_DEV_ROOT" + + # Create media directories for Sonarr/Radarr root paths + export RADARR_ROOT_PATH="$SEADEXERR_DEV_ROOT/media/movies" + export SONARR_ROOT_PATH="$SEADEXERR_DEV_ROOT/media/tv" + mkdir -p "$RADARR_ROOT_PATH" + mkdir -p "$SONARR_ROOT_PATH" + + start_service() { + local name=$1 + local cmd=$2 + local data_dir="$SEADEXERR_DEV_ROOT/$name" + mkdir -p "$data_dir" + + # Try to find the executable, fallback to name + if command -v $name &> /dev/null; then + EXEC=$name + elif command -v $cmd &> /dev/null; then + EXEC=$cmd + else + echo "Could not find executable for $name" + return + fi + + echo "Starting $name..." + $EXEC --data="$data_dir" --nobrowser > "$data_dir/$name.log" 2>&1 & + echo $! + } + + # Start services and capture PIDs + SONARR_PID=$(start_service "sonarr" "Sonarr") + RADARR_PID=$(start_service "radarr" "Radarr") + PROWLARR_PID=$(start_service "prowlarr" "Prowlarr") + + # Function to extract API key from config.xml + get_api_key() { + local name=$1 + local config_file="$SEADEXERR_DEV_ROOT/$name/config.xml" + local max_retries=30 + local count=0 + + echo "Waiting for $name to generate config..." >&2 + while [ ! -f "$config_file" ] || ! grep -q "" "$config_file"; do + sleep 1 + count=$((count+1)) + if [ $count -ge $max_retries ]; then + echo "Timeout waiting for $name config" >&2 + return + fi + done + + # Extract key between tags + grep -o ".*" "$config_file" | sed -e 's/<[^>]*>//g' + } + + # Export keys to environment + export SONARR_API_KEY=$(get_api_key "sonarr") + export RADARR_API_KEY=$(get_api_key "radarr") + export PROWLARR_API_KEY=$(get_api_key "prowlarr") + + echo "----------------------------------------" + echo "API Keys loaded:" + echo "SONARR_API_KEY: $SONARR_API_KEY" + echo "RADARR_API_KEY: $RADARR_API_KEY" + echo "PROWLARR_API_KEY: $PROWLARR_API_KEY" + echo "----------------------------------------" + + cleanup() { + echo "Stopping background services..." + [ -n "$SONARR_PID" ] && kill $SONARR_PID 2>/dev/null + [ -n "$RADARR_PID" ] && kill $RADARR_PID 2>/dev/null + [ -n "$PROWLARR_PID" ] && kill $PROWLARR_PID 2>/dev/null + } + + trap cleanup EXIT + + echo "Services running in background. Data stored in .dev-env/" + ''; + }; + } + ); +} diff --git a/src/http.rs b/src/http.rs index 681f65f..e348304 100644 --- a/src/http.rs +++ b/src/http.rs @@ -118,14 +118,35 @@ async fn torznab_handler( TorznabOperation::Unsupported(name) => name, }; - info!( - operation = operation_name, - tvdb = query.tvdb_id.as_deref(), - tmdb = query.tmdb_id.as_deref(), - season = query.season.as_deref(), - limit = query.limit, - "torznab request received" - ); + let valid = match &operation { + TorznabOperation::Caps => true, + TorznabOperation::Search => query.query.is_none() && category_filter_matches(&query.cat), + TorznabOperation::TvSearch => { + query.tvdb_identifier().is_some() && query.season_number().is_some() + } + TorznabOperation::MovieSearch => query.tmdb_identifier().is_some(), + TorznabOperation::Unsupported(_) => false, + }; + + if valid { + info!( + operation = operation_name, + tvdb = query.tvdb_id.as_deref(), + tmdb = query.tmdb_id.as_deref(), + season = query.season.as_deref(), + limit = query.limit, + "Valid torznab request received" + ); + } else { + debug!( + operation = operation_name, + tvdb = query.tvdb_id.as_deref(), + tmdb = query.tmdb_id.as_deref(), + season = query.season.as_deref(), + limit = query.limit, + "Invalid torznab request received" + ); + } match operation { TorznabOperation::Caps => respond_caps(&state),