118 lines
4.5 KiB
Bash
Executable file
118 lines
4.5 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# qbittorrent-notify.sh — Discarr disc-rip notification hook for qBittorrent
|
|
# Relative Path: ./projects/discarr/scripts/qbittorrent-notify.sh
|
|
#
|
|
# Detects VIDEO_TS / BDMV / multi-disk structures in a completed download and
|
|
# POSTs a scan job to Discarr. qBittorrent calls this on torrent completion.
|
|
#
|
|
# qBittorrent setup:
|
|
# Options → Downloads → "Run external program on torrent completion"
|
|
# Command: /path/to/qbittorrent-notify.sh "%F"
|
|
# (or set DISCARR_URL in environment and call without args using %F as $1)
|
|
#
|
|
# Environment overrides:
|
|
# DISCARR_URL — default: http://127.0.0.1:8603
|
|
# DISCARR_LOG — log file, default: /tmp/discarr-qbit.log
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Config ───────────────────────────────────────────────────────────────────
|
|
DISCARR_URL="${DISCARR_URL:-http://127.0.0.1:8603}"
|
|
DISCARR_LOG="${DISCARR_LOG:-/tmp/discarr-qbit.log}"
|
|
TORRENT_PATH="${1:-${TORRENT_CONTENT_PATH:-}}"
|
|
|
|
# ── Colors ────────────────────────────────────────────────────────────────────
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'
|
|
|
|
log() { echo -e "${CYAN}[discarr]${RESET} $*" | tee -a "$DISCARR_LOG"; }
|
|
ok() { echo -e "${GREEN}[discarr]${RESET} $*" | tee -a "$DISCARR_LOG"; }
|
|
warn() { echo -e "${YELLOW}[discarr]${RESET} $*" | tee -a "$DISCARR_LOG"; }
|
|
err() { echo -e "${RED}[discarr]${RESET} $*" | tee -a "$DISCARR_LOG"; }
|
|
|
|
# ── Validate ──────────────────────────────────────────────────────────────────
|
|
if [[ -z "$TORRENT_PATH" ]]; then
|
|
err "Usage: $0 <torrent-content-path>"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -e "$TORRENT_PATH" ]]; then
|
|
err "Path does not exist: $TORRENT_PATH"
|
|
exit 1
|
|
fi
|
|
|
|
log "Checking download: $TORRENT_PATH"
|
|
|
|
# ── Disc structure detection ──────────────────────────────────────────────────
|
|
# Walk the path (and one level deep) looking for disc layouts
|
|
detect_disc_path() {
|
|
local base="$1"
|
|
|
|
# Direct VIDEO_TS or BDMV at root
|
|
if [[ -d "$base/VIDEO_TS" ]] || [[ -d "$base/BDMV" ]]; then
|
|
echo "$base"; return 0
|
|
fi
|
|
|
|
# Multi-disk: Disk1/, Disc1/, DISK_1/ … containing VIDEO_TS or BDMV
|
|
local found_multi=0
|
|
while IFS= read -r -d '' subdir; do
|
|
local dname
|
|
dname=$(basename "$subdir")
|
|
if [[ "$dname" =~ ^[Dd]is[ck][[:space:]_-]?[0-9]+$ ]]; then
|
|
if [[ -d "$subdir/VIDEO_TS" ]] || [[ -d "$subdir/BDMV" ]]; then
|
|
found_multi=1
|
|
fi
|
|
fi
|
|
done < <(find "$base" -maxdepth 1 -mindepth 1 -type d -print0 2>/dev/null)
|
|
|
|
if [[ $found_multi -eq 1 ]]; then
|
|
echo "$base"; return 0
|
|
fi
|
|
|
|
# ISO files at root
|
|
local iso_count
|
|
iso_count=$(find "$base" -maxdepth 1 -name '*.iso' -o -name '*.ISO' 2>/dev/null | wc -l)
|
|
if [[ "$iso_count" -gt 0 ]]; then
|
|
echo "$base"; return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
DISC_PATH=""
|
|
if [[ -d "$TORRENT_PATH" ]]; then
|
|
DISC_PATH=$(detect_disc_path "$TORRENT_PATH") || true
|
|
elif [[ "$TORRENT_PATH" =~ \.[Ii][Ss][Oo]$ ]]; then
|
|
DISC_PATH=$(dirname "$TORRENT_PATH")
|
|
fi
|
|
|
|
if [[ -z "$DISC_PATH" ]]; then
|
|
log "No disc structure detected — skipping (not a disc rip)"
|
|
exit 0
|
|
fi
|
|
|
|
ok "Disc structure found at: $DISC_PATH"
|
|
|
|
# ── Notify Discarr ────────────────────────────────────────────────────────────
|
|
log "POSTing scan job to $DISCARR_URL ..."
|
|
|
|
RESPONSE=$(curl -s --max-time 10 \
|
|
-X POST "$DISCARR_URL/api/scan" \
|
|
-H 'Content-Type: application/json' \
|
|
-d "{\"path\": $(python3 -c "import json,sys; print(json.dumps(sys.argv[1]))" "$DISC_PATH")}" \
|
|
2>&1) || {
|
|
err "Failed to contact Discarr at $DISCARR_URL"
|
|
exit 1
|
|
}
|
|
|
|
JOB_ID=$(echo "$RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
|
|
|
|
if [[ -n "$JOB_ID" ]]; then
|
|
ok "Scan queued — job ID: $JOB_ID"
|
|
ok "Open Discarr to map episodes: $DISCARR_URL"
|
|
else
|
|
err "Unexpected response from Discarr: $RESPONSE"
|
|
exit 1
|
|
fi
|