Last active
July 4, 2025 22:53
-
-
Save claabs/24ee31dd39bf0c48b74ebfdba9df4f93 to your computer and use it in GitHub Desktop.
Sonarr/Radarr search for RSS grabs script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Radarr On Grab - Auto Movie Search with Job Locking | |
# Pauses any immediately obsoleted torrents | |
# Prevents recursive triggers and handles async search completion | |
# Configuration | |
RADARR_URL="http://localhost:7878" | |
RADARR_API_KEY="apikey" | |
QBITTORRENT_URL="http://172.17.0.1:8080" | |
QBITTORRENT_USER="admin" | |
QBITTORRENT_PASS="adminadmin" | |
LOG_FILE="/config/scripts/auto-search.log" | |
LOCK_DIR="/config/scripts/radarr_searches" | |
# Create working directory | |
mkdir -p "$LOCK_DIR" | |
# Check for grab event | |
if [ "$radarr_eventtype" != "Grab" ]; then | |
exit 0 | |
fi | |
# qBittorrent authentication function (same as Sonarr version) | |
authenticate_qbittorrent() { | |
local COOKIE_JAR | |
COOKIE_JAR=$(mktemp) | |
LOGIN_RESPONSE=$(curl -s -i -X POST \ | |
-b "$COOKIE_JAR" \ | |
-c "$COOKIE_JAR" \ | |
-d "username=$QBITTORRENT_USER&password=$QBITTORRENT_PASS" \ | |
"$QBITTORRENT_URL/api/v2/auth/login") | |
if ! grep -q "Fails" <<< "$LOGIN_RESPONSE"; then | |
echo "$COOKIE_JAR" | |
else | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] qBittorrent authentication failed" >> "$LOG_FILE" | |
rm -f "$COOKIE_JAR" | |
echo "" | |
fi | |
} | |
# Main processing function | |
process_grab() { | |
# Extract information | |
local START_TIMESTAMP=$(date +%s) | |
local MOVIE_ID="$radarr_movie_id" | |
local TORRENT_HASH=$(echo "$radarr_download_id" | tr '[:upper:]' '[:lower:]') | |
local LOCK_FILE="$LOCK_DIR/${MOVIE_ID}.lock" | |
# Validate input | |
if [ -z "$MOVIE_ID" ]; then | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Error: Missing movie ID" >> "$LOG_FILE" | |
return 1 | |
fi | |
# Check for existing lock | |
if [ -f "$LOCK_FILE" ]; then | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Existing search lock found for Movie ID: $MOVIE_ID" >> "$LOG_FILE" | |
return 0 | |
fi | |
# Create lock file | |
echo "$TORRENT_HASH" > "$LOCK_FILE" | |
# Launch background monitor | |
( | |
# Check for recent search for this movie (within last 5 minutes) using Radarr API | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Checking for recent Radarr MoviesSearch commands for movie $MOVIE_ID" >> "$LOG_FILE" | |
RECENT_COMMANDS=$(curl -s -H "X-Api-Key: $RADARR_API_KEY" "$RADARR_URL/api/v3/command" | jq -c '.[] | select(.name=="MoviesSearch" and (.body.movieIds | index('"$MOVIE_ID"')))') | |
NOW=$(date +%s) | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Recent commands JSON: $RECENT_COMMANDS" >> "$LOG_FILE" | |
while read -r CMD; do | |
# Skip empty lines | |
[ -z "$CMD" ] && continue | |
CMD_STARTED=$(echo "$CMD" | jq -r '.started') | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Inspecting command: $CMD" >> "$LOG_FILE" | |
CMD_STARTED_TS=$(date -d "$CMD_STARTED" +%s) | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Command started at $CMD_STARTED ($CMD_STARTED_TS)" >> "$LOG_FILE" | |
if [ $((NOW - CMD_STARTED_TS)) -lt 300 ]; then | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Found recent command within 5 minutes, skipping search" >> "$LOG_FILE" | |
rm -f "$LOCK_FILE" | |
exit 0 | |
fi | |
done <<< "$RECENT_COMMANDS" | |
# Trigger movie search | |
SEARCH_RESPONSE=$(curl -s -X POST "$RADARR_URL/api/v3/command" \ | |
-H "Content-Type: application/json" \ | |
-H "X-Api-Key: $RADARR_API_KEY" \ | |
-d "{ | |
\"name\": \"MoviesSearch\", | |
\"movieIds\": [$MOVIE_ID] | |
}") | |
COMMAND_ID=$(echo "$SEARCH_RESPONSE" | jq -r '.id') | |
if [ -z "$COMMAND_ID" ] || [ "$COMMAND_ID" == "null" ]; then | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Failed to trigger search for Movie ID: $MOVIE_ID" >> "$LOG_FILE" | |
rm -f "$LOCK_FILE" | |
return 1 | |
fi | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting background monitor for command $COMMAND_ID" >> "$LOG_FILE" | |
# Monitor search completion | |
for i in {1..30}; do | |
sleep 10 | |
COMMAND_STATUS=$(curl -s -H "X-Api-Key: $RADARR_API_KEY" "$RADARR_URL/api/v3/command/$COMMAND_ID") | |
STATUS=$(echo "$COMMAND_STATUS" | jq -r '.status') | |
if [ "$STATUS" == "completed" ]; then | |
RESULT=$(echo "$COMMAND_STATUS" | jq -r '.result') | |
if [ "$RESULT" == "successful" ]; then | |
# Pause original torrent if new releases were grabbed | |
if grep -qE '1 reports downloaded' <<< "$COMMAND_STATUS"; then | |
QB_COOKIE_JAR=$(authenticate_qbittorrent) | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Pausing torrent $TORRENT_HASH" >> "$LOG_FILE" | |
curl -s -X POST "$QBITTORRENT_URL/api/v2/torrents/stop" \ | |
-b "$QB_COOKIE_JAR" \ | |
-c "$QB_COOKIE_JAR" \ | |
-d "hashes=$TORRENT_HASH" \ | |
>> "$LOG_FILE" | |
rm -f "$QB_COOKIE_JAR" | |
fi | |
fi | |
break | |
fi | |
done | |
# Cleanup | |
rm -f "$LOCK_FILE" | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Completed monitoring for command $COMMAND_ID" >> "$LOG_FILE" | |
) >> "$LOG_FILE" 2>&1 & | |
} | |
# Main execution | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Processing grab for $radarr_movie_title (Hash: $radarr_download_id)" >> "$LOG_FILE" | |
process_grab | |
exit 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Sonarr On Grab - Auto Season Search with Job Locking | |
# Prevents recursive triggers and handles async search completion | |
# Configuration | |
SONARR_URL="http://localhost:8989" | |
SONARR_API_KEY="apikey" | |
QBITTORRENT_URL="http://172.17.0.1:8080" | |
QBITTORRENT_USER="admin" | |
QBITTORRENT_PASS="adminadmin" | |
LOG_FILE="/config/scripts/auto-season-search.log" | |
LOCK_DIR="/config/scripts/sonarr_searches" | |
# Create working directory | |
mkdir -p "$LOCK_DIR" | |
# Check for grab event | |
if [ "$sonarr_eventtype" != "Grab" ]; then | |
exit 0 | |
fi | |
# Check if this grab is from a previous search | |
if [ -n "$sonarr_download_client" ] && [ "$sonarr_download_client" == "SearchTriggered" ]; then | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Ignoring search-triggered grab for $sonarr_release_title" >> "$LOG_FILE" | |
exit 0 | |
fi | |
# qBittorrent authentication function | |
authenticate_qbittorrent() { | |
local COOKIE_JAR | |
COOKIE_JAR=$(mktemp) | |
# Authenticate and store cookies | |
LOGIN_RESPONSE=$(curl -s -i -X POST \ | |
-b "$COOKIE_JAR" \ | |
-c "$COOKIE_JAR" \ | |
-d "username=$QBITTORRENT_USER&password=$QBITTORRENT_PASS" \ | |
"$QBITTORRENT_URL/api/v2/auth/login") | |
# Check for successful authentication | |
if ! grep -q "Fails" <<< "$LOGIN_RESPONSE"; then | |
echo "$COOKIE_JAR" # Return cookie jar path on success | |
else | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] qBittorrent authentication failed" >> "$LOG_FILE" | |
rm -f "$COOKIE_JAR" | |
echo "" | |
fi | |
} | |
# Main processing function | |
process_grab() { | |
# Extract information | |
local SERIES_ID="$sonarr_series_id" | |
local SEASON_NUMBER="$sonarr_release_seasonnumber" | |
local TORRENT_HASH=$(echo "$sonarr_download_id" | tr '[:upper:]' '[:lower:]') | |
local LOCK_FILE="$LOCK_DIR/${SERIES_ID}_${SEASON_NUMBER}.lock" | |
# Validate input | |
if [ -z "$SERIES_ID" ] || [ -z "$SEASON_NUMBER" ]; then | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Error: Missing series ID or season number" >> "$LOG_FILE" | |
return 1 | |
fi | |
# Check for existing lock | |
if [ -f "$LOCK_FILE" ]; then | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Existing search lock found for S${SEASON_NUMBER}, skipping" >> "$LOG_FILE" | |
return 0 | |
fi | |
# Create lock file | |
echo "$TORRENT_HASH" > "$LOCK_FILE" | |
# Launch background monitor | |
( | |
# Get series details | |
SERIES_JSON=$(curl -s -H "X-Api-Key: $SONARR_API_KEY" "$SONARR_URL/api/v3/series/$SERIES_ID") | |
SEASON_STATS=$(echo "$SERIES_JSON" | jq ".seasons[] | select(.seasonNumber == $SEASON_NUMBER) | .statistics") | |
if [ -z "$SEASON_STATS" ]; then | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Error: Could not get season $SEASON_NUMBER stats" >> "$LOG_FILE" | |
rm -f "$LOCK_FILE" | |
exit 1 | |
fi | |
EPISODE_COUNT=$(echo "$SEASON_STATS" | jq -r '.episodeCount') | |
TOTAL_EPISODES=$(echo "$SEASON_STATS" | jq -r '.totalEpisodeCount') | |
# Only proceed if season is fully released | |
if [ "$EPISODE_COUNT" -ne "$TOTAL_EPISODES" ]; then | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Season $SEASON_NUMBER not fully released ($EPISODE_COUNT/$TOTAL_EPISODES)" >> "$LOG_FILE" | |
rm -f "$LOCK_FILE" | |
exit 0 | |
fi | |
# Check for recent search for this series and season (within last 5 minutes) using Sonarr API | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Checking for recent Sonarr SeasonSearch commands for series $SERIES_ID, season $SEASON_NUMBER" >> "$LOG_FILE" | |
RECENT_COMMANDS=$(curl -s -H "X-Api-Key: $SONARR_API_KEY" "$SONARR_URL/api/v3/command" | jq -c '.[] | select(.name=="SeasonSearch" and .body.seriesId=='"$SERIES_ID"' and .body.seasonNumber=='"$SEASON_NUMBER"')') | |
NOW=$(date +%s) | |
FOUND_RECENT="" | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Recent commands JSON: $RECENT_COMMANDS" >> "$LOG_FILE" | |
while read -r CMD; do | |
# Skip empty lines | |
[ -z "$CMD" ] && continue | |
CMD_STARTED=$(echo "$CMD" | jq -r '.started') | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Inspecting command: $CMD" >> "$LOG_FILE" | |
CMD_STARTED_TS=$(date -d "$CMD_STARTED" +%s) | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Command started at $CMD_STARTED ($CMD_STARTED_TS)" >> "$LOG_FILE" | |
if [ $((NOW - CMD_STARTED_TS)) -lt 300 ]; then | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Found recent command within 5 minutes, skipping search" >> "$LOG_FILE" | |
rm -f "$LOCK_FILE" | |
exit 0 | |
fi | |
done <<< "$RECENT_COMMANDS" | |
# Trigger async search | |
SEARCH_RESPONSE=$(curl -s -X POST "$SONARR_URL/api/v3/command" \ | |
-H "Content-Type: application/json" \ | |
-H "X-Api-Key: $SONARR_API_KEY" \ | |
-d "{ | |
\"name\": \"SeasonSearch\", | |
\"seriesId\": $SERIES_ID, | |
\"seasonNumber\": $SEASON_NUMBER | |
}") | |
COMMAND_ID=$(echo "$SEARCH_RESPONSE" | jq -r '.id') | |
if [ -z "$COMMAND_ID" ] || [ "$COMMAND_ID" == "null" ]; then | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Failed to trigger search for S${SEASON_NUMBER}" >> "$LOG_FILE" | |
rm -f "$LOCK_FILE" | |
return 1 | |
fi | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting background monitor for command $COMMAND_ID" >> "$LOG_FILE" | |
# Monitor search completion | |
for i in {1..30}; do | |
sleep 10 | |
COMMAND_STATUS=$(curl -s -H "X-Api-Key: $SONARR_API_KEY" "$SONARR_URL/api/v3/command/$COMMAND_ID") | |
STATUS=$(echo "$COMMAND_STATUS" | jq -r '.status') | |
if [ "$STATUS" == "completed" ]; then | |
RESULT=$(echo "$COMMAND_STATUS" | jq -r '.result') | |
if [ "$RESULT" == "successful" ]; then | |
# Pause original torrent if new releases were grabbed | |
if grep -qE '1 reports downloaded' <<< "$COMMAND_STATUS"; then | |
QB_COOKIE_JAR=$(authenticate_qbittorrent) | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Pausing torrent $TORRENT_HASH" >> "$LOG_FILE" | |
curl -s -X POST "$QBITTORRENT_URL/api/v2/torrents/stop" \ | |
-b "$QB_COOKIE_JAR" \ | |
-c "$QB_COOKIE_JAR" \ | |
-d "hashes=$TORRENT_HASH" \ | |
>> "$LOG_FILE" | |
rm -f "$QB_COOKIE_JAR" | |
fi | |
fi | |
break | |
fi | |
done | |
# Cleanup | |
rm -f "$LOCK_FILE" | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Completed monitoring for command $COMMAND_ID" >> "$LOG_FILE" | |
) >> "$LOG_FILE" 2>&1 & | |
} | |
# Main execution | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Processing grab for $sonarr_release_title (Hash: $sonarr_download_id)" >> "$LOG_FILE" | |
process_grab | |
exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment