#!/bin/bash # Bulk-add orphaned shows to Sonarr from dry-run output # Usage: sonarr_bulk_add.sh [--add] (default: dry-run) SONARR_URL="http://localhost:8989" SONARR_API_KEY="130decd3db5b4c25afad64864cd03f9f" ORPHAN_FILE="/tmp/sonarr_dryrun3.txt" ADD_MODE=false [[ "${1:-}" == "--add" ]] && ADD_MODE=true declare -A PATH_ROOT=( ["/mnt/user/Tv_Shows"]="/tv" ["/mnt/user/Kids_Tv_Shows"]="/kids tv" ["/mnt/user/Anime_Shows-Old"]="/ext-anime-shows" ) declare -A PATH_QUALITY=( ["/mnt/user/Tv_Shows"]="6" ["/mnt/user/Kids_Tv_Shows"]="2" ["/mnt/user/Anime_Shows-Old"]="6" ) declare -A PATH_TYPE=( ["/mnt/user/Tv_Shows"]="standard" ["/mnt/user/Kids_Tv_Shows"]="standard" ["/mnt/user/Anime_Shows-Old"]="anime" ) sonarr_get() { curl -sf -H "X-Api-Key: $SONARR_API_KEY" "${SONARR_URL}/api/v3/${1}"; } sonarr_post() { curl -sf -X POST -H "X-Api-Key: $SONARR_API_KEY" -H "Content-Type: application/json" -d "$2" "${SONARR_URL}/api/v3/${1}"; } # Build unique list: "fs_root|show_name" declare -A SEEN declare -a SHOWS while IFS= read -r line; do fs_root=$(echo "$line" | grep -oP '/mnt/user/[^/]+') show_name=$(echo "$line" | grep -oP '/mnt/user/[^/]+/\K[^/]+') key="${fs_root}|${show_name}" [[ -z "$show_name" || -z "$fs_root" ]] && continue [[ -n "${SEEN[$key]:-}" ]] && continue SEEN["$key"]=1 SHOWS+=("$key") done < <(grep "ORPHAN" "$ORPHAN_FILE") # Get existing series titles in Sonarr EXISTING=$(sonarr_get "series" | jq -r '.[].title' | sort) ADDED=0 SKIPPED=0 NOT_FOUND=0 ALREADY=0 for entry in "${SHOWS[@]}"; do fs_root="${entry%%|*}" show_name="${entry##*|}" root_path="${PATH_ROOT[$fs_root]:-}" quality_id="${PATH_QUALITY[$fs_root]:-6}" series_type="${PATH_TYPE[$fs_root]:-standard}" if [[ -z "$root_path" ]]; then echo "SKIP (unknown path): $show_name ($fs_root)" (( SKIPPED++ )) continue fi # Check if already in Sonarr if echo "$EXISTING" | grep -qi "^${show_name}$"; then echo "ALREADY: $show_name" (( ALREADY++ )) continue fi # Search TVDB via Sonarr lookup encoded=$(jq -rn --arg s "$show_name" '$s | @uri') result=$(sonarr_get "series/lookup?term=${encoded}" 2>/dev/null) if [[ -z "$result" || "$result" == "[]" ]]; then echo "NOT FOUND: $show_name" (( NOT_FOUND++ )) continue fi # Pick best match: exact title, then first result best=$(echo "$result" | jq --arg name "$show_name" ' . as $all | (map(select(.title | ascii_downcase == ($name | ascii_downcase))) | first) // $all[0] ') tvdb_id=$(echo "$best" | jq -r '.tvdbId // empty') matched_title=$(echo "$best" | jq -r '.title // empty') year=$(echo "$best" | jq -r '.year // 0') if [[ -z "$tvdb_id" ]]; then echo "NOT FOUND: $show_name" (( NOT_FOUND++ )) continue fi if [[ "$ADD_MODE" == false ]]; then echo "WOULD ADD: $show_name → \"$matched_title\" ($year) [tvdb:$tvdb_id] [root:$root_path] [type:$series_type]" (( ADDED++ )) continue fi # Build payload from lookup result + our settings payload=$(echo "$best" | jq \ --arg root "$root_path" \ --argjson qid "$quality_id" \ --arg stype "$series_type" \ '{ tvdbId: .tvdbId, title: .title, titleSlug: .titleSlug, images: .images, seasons: .seasons, year: .year, rootFolderPath: $root, qualityProfileId: $qid, seriesType: $stype, seasonFolder: true, monitored: true, addOptions: { monitor: "all", searchForMissingEpisodes: false, searchForCutoffUnmetEpisodes: false } }') response=$(sonarr_post "series" "$payload" 2>&1) if echo "$response" | jq -e '.id' &>/dev/null; then echo "ADDED: \"$matched_title\" ($year) [tvdb:$tvdb_id]" (( ADDED++ )) else err=$(echo "$response" | jq -r '.[0].errorMessage // .message // "unknown error"' 2>/dev/null) echo "FAILED: $show_name → $err" (( SKIPPED++ )) fi done echo "" echo "━━━ Summary ━━━" if [[ "$ADD_MODE" == false ]]; then echo "Would add: $ADDED" else echo "Added: $ADDED" fi echo "Already in: $ALREADY" echo "Not found: $NOT_FOUND" echo "Skipped: $SKIPPED"