Skip to content

Instantly share code, notes, and snippets.

@claabs
Last active July 4, 2025 22:53
Show Gist options
  • Save claabs/24ee31dd39bf0c48b74ebfdba9df4f93 to your computer and use it in GitHub Desktop.
Save claabs/24ee31dd39bf0c48b74ebfdba9df4f93 to your computer and use it in GitHub Desktop.
Sonarr/Radarr search for RSS grabs script
#!/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
#!/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