# shellcheck shell=bash # Sourced helper for talking to the Plex Media Server HTTP API. # Not executable — source it: . "$COLLECTION_DIR/lib/plex-api.sh" # # Provides: plex_load_env, plex_require_deps, plex_get, plex_get_raw, plex_put, # plex_check_auth, plex_log, plex_warn, plex_die plex_log() { printf '%s\n' "$*"; } plex_warn() { printf '%s\n' "$*" >&2; } plex_die() { plex_warn "error: $*"; exit 1; } # Load the collection's .env into the environment. Silent if absent — the # caller's :? checks produce the actionable message. plex_load_env() { local env_file="${1:?plex_load_env needs a path}" [[ -f "$env_file" ]] || return 0 set -a; . "$env_file"; set +a } plex_require_deps() { local missing=() local dep for dep in curl jq; do command -v "$dep" >/dev/null 2>&1 || missing+=("$dep") done (( ${#missing[@]} == 0 )) || plex_die "missing required tools: ${missing[*]}" } # Assemble the curl flags shared by every request. PLEX_INSECURE=1 adds -k; # plex.bergerhouse.net verifies cleanly from `dev`, so it should stay 0. _plex_curl_flags() { local -n out=$1 out=(--silent --show-error --location-trusted --max-time "${PLEX_TIMEOUT:-30}") [[ "${PLEX_INSECURE:-0}" == "1" ]] && out+=(--insecure) return 0 } # plex_get [query...] -> JSON on stdout # Path is relative to PLEX_URL and must start with '/'. Extra args are passed to # curl, so use --get --data-urlencode 'k=v' for query parameters. plex_get() { local path="${1:?plex_get needs a path}"; shift local flags; _plex_curl_flags flags curl "${flags[@]}" \ -H 'Accept: application/json' \ -H "X-Plex-Token: ${PLEX_TOKEN}" \ "$@" \ "${PLEX_URL%/}${path}" } # Same as plex_get but returns the server's default XML — a few endpoints # (notably /library/sections//all with includeGuids) are richer in XML. plex_get_raw() { local path="${1:?plex_get_raw needs a path}"; shift local flags; _plex_curl_flags flags curl "${flags[@]}" \ -H "X-Plex-Token: ${PLEX_TOKEN}" \ "$@" \ "${PLEX_URL%/}${path}" } # plex_put [curl args...] — mutating request. Callers are responsible for # honouring --dry-run before calling this. plex_put() { local path="${1:?plex_put needs a path}"; shift local flags; _plex_curl_flags flags curl "${flags[@]}" -X PUT \ -H 'Accept: application/json' \ -H "X-Plex-Token: ${PLEX_TOKEN}" \ "$@" \ "${PLEX_URL%/}${path}" } # Fail fast with a useful message rather than letting every later call return # an empty body. Plex answers 401 with an HTML body for a bad/absent token. plex_check_auth() { : "${PLEX_URL:?PLEX_URL is not set — copy .env.example to .env}" : "${PLEX_TOKEN:?PLEX_TOKEN is not set — copy .env.example to .env}" [[ "$PLEX_TOKEN" != "replace-me" ]] || plex_die "PLEX_TOKEN is still the placeholder value" local flags; _plex_curl_flags flags local code code="$(curl "${flags[@]}" -o /dev/null -w '%{http_code}' \ -H "X-Plex-Token: ${PLEX_TOKEN}" "${PLEX_URL%/}/identity")" \ || plex_die "cannot reach ${PLEX_URL} (Tailscale down?)" [[ "$code" == "200" ]] || plex_die "${PLEX_URL}/identity returned HTTP ${code}" code="$(curl "${flags[@]}" -o /dev/null -w '%{http_code}' \ -H "X-Plex-Token: ${PLEX_TOKEN}" "${PLEX_URL%/}/library/sections")" case "$code" in 200) return 0 ;; 401) plex_die "PLEX_TOKEN rejected (HTTP 401) — the token is wrong or expired" ;; *) plex_die "/library/sections returned HTTP ${code}" ;; esac }