mirror of
https://github.com/soulis-1256/assemblrr.git
synced 2026-08-29 17:41:40 +00:00
Pick extra subtitle languages in setup and config edit (Jellyfin preferred first, Bazarr downloads all). Config edit OpenSubtitles goes straight to username/password, ignores leftover fzf Enter, and cancel leaves secrets.
594 lines
20 KiB
Bash
594 lines
20 KiB
Bash
#!/bin/bash
|
|
# fzf-based TUI for selections (Prowlarr indexers, Bazarr providers, languages, paths)
|
|
# Provides reusable fzf selection patterns for assemblrr.
|
|
# Sets SELECTED_INDEXERS / SELECTED_SUBTITLE_PROVIDERS / SELECTED_SUBTITLE_LANGUAGES arrays.
|
|
# Usage: source lib/fzf-tui.sh; configure_indexers "$api_key"
|
|
|
|
set -euo pipefail
|
|
|
|
_fzf_tui_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=/dev/null
|
|
[ -f "$_fzf_tui_dir/ui.sh" ] && source "$_fzf_tui_dir/ui.sh"
|
|
|
|
SELECTED_INDEXERS=()
|
|
SELECTED_SUBTITLE_PROVIDERS=()
|
|
SELECTED_SUBTITLE_LANGUAGES=()
|
|
|
|
# Stored picker codes, preferred first. SUBTITLE_LANGUAGES="eng,ell" wins;
|
|
# otherwise SUBTITLE_LANGUAGE (legacy single value).
|
|
_subtitle_stored_lang_codes() {
|
|
local raw="${SUBTITLE_LANGUAGES:-${SUBTITLE_LANGUAGE:-}}"
|
|
raw=${raw// /}
|
|
[ -z "$raw" ] && return 0
|
|
echo "$raw" | tr ',' '\n' | awk 'NF && !seen[$0]++'
|
|
}
|
|
|
|
# $1 = preferred code; remaining args = the full set (may include preferred).
|
|
# Prints comma-separated, preferred first, unique.
|
|
_subtitle_join_preferred_first() {
|
|
local preferred="${1:-}"
|
|
shift || true
|
|
local -a out=()
|
|
local c
|
|
[ -n "$preferred" ] && out+=("$preferred")
|
|
for c in "$@"; do
|
|
[ -z "$c" ] && continue
|
|
[ "$c" = "$preferred" ] && continue
|
|
out+=("$c")
|
|
done
|
|
(IFS=,; echo "${out[*]}")
|
|
}
|
|
|
|
# --- TTY detection (reused across functions) ---
|
|
_fzf_has_tty() {
|
|
[ -t 0 ] && [ -t 2 ]
|
|
}
|
|
|
|
_fzf_can_render() {
|
|
command -v fzf &>/dev/null && echo "test" | timeout 1 fzf --height=5 --no-mouse --filter="test" &>/dev/null
|
|
}
|
|
|
|
# TSV: key<TAB>title<TAB>detail<TAB>install<TAB>media
|
|
# Extra args after the two defaults are injected storage roots (tests).
|
|
format_path_menu() {
|
|
local def_install="$1"
|
|
local def_media="$2"
|
|
shift 2
|
|
local app="${APP_NAME:-assemblrr}"
|
|
local -a roots=()
|
|
local root title free
|
|
|
|
if [ "$#" -gt 0 ]; then
|
|
roots=("$@")
|
|
else
|
|
mapfile -t roots < <(list_storage_roots)
|
|
fi
|
|
|
|
printf '%s\t%s\t%s\t%s\t%s\n' \
|
|
"home" "Home (default)" "${def_install} + ${def_media}" \
|
|
"$def_install" "$def_media"
|
|
|
|
for root in "${roots[@]}"; do
|
|
[ -n "$root" ] || continue
|
|
root="${root%/}"
|
|
[ "$root" = "${HOME:-}" ] && continue
|
|
title=$(storage_root_label "$root")
|
|
free=$(storage_free_label "$root")
|
|
printf '%s\t%s\t%s\t%s\t%s\n' \
|
|
"all:${root}" "${title} — everything here" \
|
|
"${root}/${app} + ${root}/${app}-media${free}" \
|
|
"${root}/${app}" "${root}/${app}-media"
|
|
printf '%s\t%s\t%s\t%s\t%s\n' \
|
|
"media:${root}" "${title} — media only (recommended)" \
|
|
"${def_install} + ${root}/${app}-media${free}" \
|
|
"$def_install" "${root}/${app}-media"
|
|
done
|
|
|
|
printf '%s\t%s\t%s\t%s\t%s\n' \
|
|
"browse" "Browse for a folder..." \
|
|
"Pick a parent, then choose everything vs media-only" "" ""
|
|
printf '%s\t%s\t%s\t%s\t%s\n' \
|
|
"custom" "Type paths..." \
|
|
"Enter install and media directories yourself" "" ""
|
|
}
|
|
|
|
_fzf_select_path_row() {
|
|
local def_install="${1:-}"
|
|
local def_media="${2:-}"
|
|
local data selected="" gen=""
|
|
poke_wsl_automounts 2>/dev/null || true
|
|
data=$(format_path_menu "$def_install" "$def_media")
|
|
if _fzf_can_render && _fzf_has_tty; then
|
|
gen=$(mktemp)
|
|
cat > "$gen" <<EOF
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
# shellcheck source=/dev/null
|
|
source $(printf '%q' "$_fzf_tui_dir/core.sh")
|
|
# shellcheck source=/dev/null
|
|
source $(printf '%q' "$_fzf_tui_dir/fzf-tui.sh")
|
|
poke_wsl_automounts 2>/dev/null || true
|
|
format_path_menu $(printf '%q' "$def_install") $(printf '%q' "$def_media")
|
|
EOF
|
|
chmod +x "$gen"
|
|
selected=$(echo "$data" | fzf \
|
|
--delimiter=$'\t' \
|
|
--with-nth=2,3 \
|
|
--prompt="Install location> " \
|
|
--header="↑↓=navigate Enter=confirm r=refresh drives Esc=home default" \
|
|
--bind="r:reload($gen)+clear-query" \
|
|
--height=60% \
|
|
--reverse \
|
|
--border \
|
|
--scrollbar='│' \
|
|
2>/dev/null) || true
|
|
rm -f "$gen"
|
|
echo "$selected"
|
|
return 0
|
|
fi
|
|
|
|
local i=0 line
|
|
echo "$data" | while IFS=$'\t' read -r _ title detail _; do
|
|
i=$((i + 1))
|
|
printf ' %d) %s\n %s\n' "$i" "$title" "$detail" >&2
|
|
done
|
|
echo "Enter number (default: 1 = home): " >&2
|
|
local input=""
|
|
read -r input || true
|
|
input="${input:-1}"
|
|
if [[ "$input" =~ ^[0-9]+$ ]]; then
|
|
echo "$data" | awk -F '\t' -v n="$input" 'NR==n {print}'
|
|
fi
|
|
}
|
|
|
|
_prompt_parent_usage() {
|
|
local parent="$1"
|
|
local def_install="$2"
|
|
local app="${APP_NAME:-assemblrr}"
|
|
local choice=""
|
|
echo
|
|
echo "Use ${parent} for:"
|
|
echo " 1) Media only (recommended) — config stays in ${def_install}"
|
|
echo " 2) Everything — ${parent}/${app} + ${parent}/${app}-media"
|
|
read -p "Choose [1]: " choice
|
|
choice="${choice:-1}"
|
|
case "$choice" in
|
|
2)
|
|
install_directory="${parent}/${app}"
|
|
media_directory="${parent}/${app}-media"
|
|
;;
|
|
*)
|
|
install_directory="$def_install"
|
|
media_directory="${parent}/${app}-media"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_browse_parent_folder() {
|
|
local app="${APP_NAME:-assemblrr}"
|
|
local -a roots=()
|
|
local r selected="" list
|
|
list=$(mktemp)
|
|
mapfile -t roots < <(list_storage_roots)
|
|
{
|
|
printf '%s\n' "${HOME:-/}"
|
|
printf '%s\n' "${roots[@]}"
|
|
for r in "${HOME:-/}" "${roots[@]}"; do
|
|
[ -d "$r" ] || continue
|
|
if [ "$r" = "/mnt/c" ]; then
|
|
find "$r" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | head -80
|
|
else
|
|
find "$r" -mindepth 1 -maxdepth 2 -type d 2>/dev/null | head -200
|
|
fi
|
|
done
|
|
} | sort -u > "$list"
|
|
|
|
if _fzf_can_render && _fzf_has_tty; then
|
|
selected=$(fzf \
|
|
--prompt="Parent folder> " \
|
|
--header="This folder will contain ${app} / ${app}-media. Esc=cancel" \
|
|
--height=60% --reverse --border \
|
|
< "$list" 2>/dev/null) || true
|
|
else
|
|
echo "Enter a parent folder path (blank to cancel): " >&2
|
|
read -r selected || true
|
|
fi
|
|
rm -f "$list"
|
|
echo "$selected"
|
|
}
|
|
|
|
_prompt_typed_paths() {
|
|
local def_install="$1"
|
|
local def_media="$2"
|
|
read -p "Installation directory? [${def_install}]: " install_directory
|
|
install_directory=${install_directory:-$def_install}
|
|
install_directory=$(expand_path "$install_directory")
|
|
read -p "Media directory? [${def_media}]: " media_directory
|
|
media_directory=${media_directory:-$def_media}
|
|
media_directory=$(expand_path "$media_directory")
|
|
}
|
|
|
|
# Sets install_directory and media_directory from an fzf (or numbered) menu.
|
|
pick_storage_paths() {
|
|
local def_install="${APP_DEFAULT_INSTALL_DIR:-$HOME/${APP_NAME:-assemblrr}}"
|
|
local def_media="${APP_DEFAULT_MEDIA_DIR:-$HOME/${APP_NAME:-assemblrr}-media}"
|
|
local data row key parent
|
|
|
|
if [ "${ASSEMBLRR_NONINTERACTIVE:-0}" = "1" ]; then
|
|
install_directory="$def_install"
|
|
media_directory="$def_media"
|
|
return 0
|
|
fi
|
|
|
|
while true; do
|
|
row=$(_fzf_select_path_row "$def_install" "$def_media")
|
|
if [ -z "$row" ]; then
|
|
install_directory="$def_install"
|
|
media_directory="$def_media"
|
|
return 0
|
|
fi
|
|
key=$(printf '%s\n' "$row" | cut -f1)
|
|
case "$key" in
|
|
home)
|
|
install_directory=$(printf '%s\n' "$row" | cut -f4)
|
|
media_directory=$(printf '%s\n' "$row" | cut -f5)
|
|
return 0
|
|
;;
|
|
all:*|media:*)
|
|
install_directory=$(printf '%s\n' "$row" | cut -f4)
|
|
media_directory=$(printf '%s\n' "$row" | cut -f5)
|
|
return 0
|
|
;;
|
|
browse)
|
|
parent=$(_browse_parent_folder)
|
|
if [ -z "$parent" ]; then
|
|
continue
|
|
fi
|
|
parent=$(expand_path "$parent")
|
|
if [ ! -d "$parent" ]; then
|
|
log_warning "Not a directory: $parent"
|
|
continue
|
|
fi
|
|
_prompt_parent_usage "$parent" "$def_install"
|
|
return 0
|
|
;;
|
|
custom)
|
|
_prompt_typed_paths "$def_install" "$def_media"
|
|
return 0
|
|
;;
|
|
*)
|
|
install_directory="$def_install"
|
|
media_directory="$def_media"
|
|
return 0
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Single-select from TSV rows (first column is the value).
|
|
# Args: $1 = data, $2 = prompt, $3 = header, $4 = default (Esc / empty / no TTY)
|
|
# Puts the default row first so fzf highlights it.
|
|
fzf_single_select_data() {
|
|
local data="$1"
|
|
local prompt="${2:-Select> }"
|
|
local header="${3:-↑↓=navigate Enter=confirm Esc=default}"
|
|
local default="${4:-}"
|
|
|
|
if [ -z "$data" ]; then
|
|
echo "$default"
|
|
return 0
|
|
fi
|
|
|
|
if [ -n "$default" ]; then
|
|
local preferred_row rest
|
|
preferred_row=$(printf '%s\n' "$data" | awk -F'\t' -v d="$default" '$1 == d { print; exit }')
|
|
rest=$(printf '%s\n' "$data" | awk -F'\t' -v d="$default" '$1 != d { print }')
|
|
if [ -n "$preferred_row" ]; then
|
|
data="$preferred_row"
|
|
[ -n "$rest" ] && data+=$'\n'"$rest"
|
|
fi
|
|
fi
|
|
|
|
if ! _fzf_has_tty; then
|
|
echo "$data" | head -25 >&2
|
|
echo "Enter selection (default: ${default:-first}): " >&2
|
|
local input=""
|
|
read -r input || true
|
|
if [ -z "$input" ]; then
|
|
if [ -n "$default" ]; then
|
|
echo "$default"
|
|
else
|
|
echo "$data" | head -1 | cut -f1
|
|
fi
|
|
else
|
|
echo "$input" | cut -f1
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
local selected
|
|
selected=$(echo "$data" | fzf \
|
|
--prompt="$prompt" \
|
|
--height=40% \
|
|
--reverse \
|
|
--border \
|
|
--header="$header" \
|
|
--delimiter=$'\t' \
|
|
--with-nth=1..2 \
|
|
--scrollbar='│' \
|
|
2>/dev/null | cut -f1) || true
|
|
|
|
if [ -z "$selected" ]; then
|
|
echo "$default"
|
|
else
|
|
echo "$selected"
|
|
fi
|
|
}
|
|
|
|
# Format Prowlarr schema JSON + existing-name JSON array into TSV menu rows:
|
|
# name<TAB>badges<TAB>description
|
|
# Badges: [already enabled], [P] private (schema enable=false).
|
|
format_prowlarr_indexer_menu() {
|
|
local schema="$1"
|
|
local existing_json="${2:-[]}"
|
|
[ -n "$existing_json" ] || existing_json='[]'
|
|
echo "$schema" | jq -r --argjson existing "$existing_json" '
|
|
sort_by(.name | ascii_downcase)[] |
|
|
. as $i |
|
|
(($existing | index($i.name)) != null) as $on |
|
|
($i.enable | not) as $priv |
|
|
[
|
|
$i.name,
|
|
([if $on then "[already enabled]" else empty end, if $priv then "[P]" else empty end] | join(" ")),
|
|
(($i.description // "") | gsub("\n"; " ") | .[0:120])
|
|
] | @tsv
|
|
' 2>/dev/null || true
|
|
}
|
|
|
|
# fzf --bind start:… string that pre-selects rows whose first TSV field is in $2
|
|
# (newline-separated names). Empty if nothing matches.
|
|
fzf_preselect_start_bind() {
|
|
local data="$1"
|
|
local names="$2"
|
|
[ -n "$data" ] && [ -n "$names" ] || return 0
|
|
local i=0 name binds=""
|
|
while IFS=$'\t' read -r name _; do
|
|
i=$((i + 1))
|
|
[ -n "$name" ] || continue
|
|
if printf '%s\n' "$names" | grep -q -F -x "$name"; then
|
|
if [ -n "$binds" ]; then
|
|
binds="${binds}+pos(${i})+select"
|
|
else
|
|
binds="pos(${i})+select"
|
|
fi
|
|
fi
|
|
done <<< "$data"
|
|
[ -n "$binds" ] && echo "start:${binds}"
|
|
}
|
|
|
|
# --- Generic multi-select fzf ---
|
|
# Args: $1 = tab-separated list lines
|
|
# $2 = prompt (optional)
|
|
# $3 = header (optional)
|
|
# $4 = result array name (optional; default SELECTED_INDEXERS)
|
|
# $5 = noun for log messages (optional; default "item")
|
|
# $6 = newline-separated names to pre-select (optional)
|
|
# Sets FZF_SELECT_STATUS=confirmed|cancelled.
|
|
# Return 0 on confirm (array may be empty), 1 on cancel (Esc).
|
|
# Appends selected first-column values into the named array.
|
|
fzf_multi_select() {
|
|
local data="$1"
|
|
local prompt="${2:-Select items> }"
|
|
local header="${3:-Enter/TAB=toggle Ctrl-O=confirm Esc=cancel}"
|
|
local result_var="${4:-SELECTED_INDEXERS}"
|
|
local noun="${5:-item}"
|
|
local preselect_names="${6:-}"
|
|
local has_tty=false
|
|
local -n _fzf_result_ref="$result_var"
|
|
FZF_SELECT_STATUS="cancelled"
|
|
|
|
_fzf_has_tty && has_tty=true
|
|
|
|
if [ -z "$data" ]; then
|
|
return 1
|
|
fi
|
|
|
|
if _fzf_can_render && $has_tty; then
|
|
local selected fzf_rc=0
|
|
local -a fzf_args=(
|
|
--multi
|
|
--prompt="$prompt"
|
|
--delimiter=$'\t'
|
|
--with-nth=1..2
|
|
--bind='enter:toggle'
|
|
--bind='tab:toggle'
|
|
--bind='ctrl-o:accept'
|
|
--header="$header"
|
|
--height=80%
|
|
--layout=reverse
|
|
--border
|
|
)
|
|
local start_bind
|
|
start_bind=$(fzf_preselect_start_bind "$data" "$preselect_names")
|
|
if [ -n "$start_bind" ]; then
|
|
fzf_args+=(--sync --bind="$start_bind")
|
|
fi
|
|
selected=$(echo "$data" | fzf "${fzf_args[@]}" 2>/dev/null) || fzf_rc=$?
|
|
if [ "$fzf_rc" -ne 0 ]; then
|
|
FZF_SELECT_STATUS="cancelled"
|
|
return 1
|
|
fi
|
|
selected=$(printf '%s\n' "$selected" | cut -f1)
|
|
FZF_SELECT_STATUS="confirmed"
|
|
if [ -n "$selected" ]; then
|
|
while IFS= read -r idx; do
|
|
[ -n "$idx" ] && _fzf_result_ref+=("$idx")
|
|
done <<< "$selected"
|
|
echo "Selected ${#_fzf_result_ref[@]} ${noun}(s): $(IFS=,; echo "${_fzf_result_ref[*]}")" >&2
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
echo "No interactive terminal available — listing available ${noun}s." >&2
|
|
echo "$data" | cut -f1 | head -40 | sed 's/^/ /' >&2
|
|
echo "Enter ${noun} names separated by spaces (or blank to cancel):" >&2
|
|
local input_line=""
|
|
read -r input_line || true
|
|
if [ -z "${input_line:-}" ]; then
|
|
FZF_SELECT_STATUS="cancelled"
|
|
return 1
|
|
fi
|
|
FZF_SELECT_STATUS="confirmed"
|
|
for idx in $input_line; do
|
|
_fzf_result_ref+=("$idx")
|
|
done
|
|
return 0
|
|
}
|
|
|
|
# --- Prowlarr Indexer Selection ---
|
|
configure_indexers() {
|
|
local provided_key="$1"
|
|
FZF_SELECT_STATUS="cancelled"
|
|
if [ "${ASSEMBLRR_NONINTERACTIVE:-0}" = "1" ]; then
|
|
log_info "Skipping Prowlarr indexer picker (non-interactive)."
|
|
return 0
|
|
fi
|
|
ui_intro \
|
|
"Optional: pick Prowlarr indexers now, or skip and use the Prowlarr UI later." \
|
|
"Add or remove Prowlarr indexers. Esc keeps the current set."
|
|
log_info "Only enable sources you are authorized to use."
|
|
|
|
# Try to fetch indexer list from a running Prowlarr instance
|
|
local prowlarr_key="${provided_key:-}"
|
|
local indexer_data=""
|
|
|
|
if [ -z "$prowlarr_key" ]; then
|
|
if command -v docker &>/dev/null && docker ps --format '{{.Names}}' 2>/dev/null | grep -q prowlarr; then
|
|
prowlarr_key=$(docker exec prowlarr cat /config/config.xml 2>/dev/null | sed -n 's/.*<ApiKey>\(.*\)<\/ApiKey>.*/\1/p' | head -1 || true)
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$prowlarr_key" ]; then
|
|
indexer_data=$(curl -s --connect-timeout 3 \
|
|
-H "X-Api-Key: $prowlarr_key" \
|
|
"http://localhost:9696/api/v1/indexer/schema" 2>/dev/null || true)
|
|
fi
|
|
|
|
if [ -n "$indexer_data" ] && echo "$indexer_data" | jq -e 'type == "array"' >/dev/null 2>&1; then
|
|
local existing_indexers existing_json formatted preselect_names
|
|
existing_indexers=$(curl -s --connect-timeout 3 \
|
|
-H "X-Api-Key: $prowlarr_key" \
|
|
"http://localhost:9696/api/v1/indexer" 2>/dev/null || true)
|
|
existing_json=$(echo "${existing_indexers:-}" | jq -c '[.[].name // empty]' 2>/dev/null || echo '[]')
|
|
[ -n "$existing_json" ] || existing_json='[]'
|
|
formatted=$(format_prowlarr_indexer_menu "$indexer_data" "$existing_json")
|
|
preselect_names=$(echo "$existing_json" | jq -r '.[]' 2>/dev/null || true)
|
|
local prompt header
|
|
if ui_is_edit; then
|
|
prompt="Prowlarr indexers> "
|
|
header="Enter/TAB=toggle Ctrl-O=save Esc=cancel [P]=private"
|
|
else
|
|
prompt="Prowlarr indexers (optional)> "
|
|
header="Enter/TAB=toggle Ctrl-O=confirm Esc=skip [P]=private"
|
|
fi
|
|
fzf_multi_select "$formatted" \
|
|
"$prompt" \
|
|
"$header" \
|
|
SELECTED_INDEXERS \
|
|
"indexer" \
|
|
"$preselect_names" || true
|
|
ui_picker_done "indexers"
|
|
else
|
|
if ui_is_edit; then
|
|
log_info "Prowlarr is not responding — cannot edit indexers."
|
|
else
|
|
log_info "Prowlarr is not responding — skipping optional indexer setup."
|
|
log_info "You can add indexers later with: ${APP_CLI_NAME:-assemblrr} config edit"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# List subtitle provider keys from the running Bazarr container (Bazarr's catalog, not ours).
|
|
# Skips internal modules only (mixins/utils/helpers), not branded providers.
|
|
_list_bazarr_providers_from_container() {
|
|
local dirs=(
|
|
/app/bazarr/bin/custom_libs/subliminal_patch/providers
|
|
/app/bazarr/custom_libs/subliminal_patch/providers
|
|
)
|
|
local d names=""
|
|
if ! command -v docker &>/dev/null; then
|
|
return 1
|
|
fi
|
|
if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -qx 'bazarr'; then
|
|
return 1
|
|
fi
|
|
for d in "${dirs[@]}"; do
|
|
names=$(docker exec bazarr sh -c \
|
|
"ls -1 '${d}'/*.py 2>/dev/null | xargs -n1 basename 2>/dev/null" 2>/dev/null || true)
|
|
if [ -n "$names" ]; then
|
|
echo "$names" | sed 's/\.py$//' | grep -vE '^(mixins|utils|avistaz_network|_.*)$' | sort -u
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# --- Bazarr subtitle provider selection (live from container, same idea as Prowlarr) ---
|
|
configure_subtitle_providers() {
|
|
SELECTED_SUBTITLE_PROVIDERS=()
|
|
FZF_SELECT_STATUS="cancelled"
|
|
if [ "${ASSEMBLRR_NONINTERACTIVE:-0}" = "1" ]; then
|
|
log_info "Skipping Bazarr provider picker (non-interactive)."
|
|
return 0
|
|
fi
|
|
ui_intro \
|
|
"Optional: pick Bazarr subtitle providers now, or skip and use the Bazarr UI later." \
|
|
"Choose which Bazarr subtitle providers to enable. Esc keeps the current set."
|
|
ui_note \
|
|
"Catalog comes from the running Bazarr image. OpenSubtitles.com is enabled automatically only if you saved credentials." \
|
|
"Catalog comes from the running Bazarr image."
|
|
|
|
local names formatted
|
|
if ! names=$(_list_bazarr_providers_from_container); then
|
|
if ui_is_edit; then
|
|
log_info "Bazarr is not running — cannot edit providers."
|
|
else
|
|
log_info "Bazarr is not running — skipping optional provider setup."
|
|
log_info "You can set providers later with: ${APP_CLI_NAME:-assemblrr} config edit"
|
|
fi
|
|
return 0
|
|
fi
|
|
if [ -z "$names" ]; then
|
|
log_info "No providers found in the Bazarr image — skipping."
|
|
return 0
|
|
fi
|
|
|
|
formatted=$(echo "$names" | awk '{print $1 "\t"}')
|
|
local prompt header preselect_names=""
|
|
if ui_is_edit; then
|
|
prompt="Bazarr providers> "
|
|
header="Enter/TAB=toggle Ctrl-O=save Esc=cancel"
|
|
if type _bazarr_config_file >/dev/null 2>&1 && type _bazarr_extract_apikey >/dev/null 2>&1; then
|
|
local bcfg bkey
|
|
bcfg=$(_bazarr_config_file 2>/dev/null || true)
|
|
if [ -n "$bcfg" ]; then
|
|
bkey=$(_bazarr_extract_apikey "$bcfg")
|
|
fi
|
|
if [ -n "${bkey:-}" ]; then
|
|
preselect_names=$(curl -s --connect-timeout 5 \
|
|
"http://${API_HOST:-127.0.0.1}:${BAZARR_PORT:-6767}/api/system/settings?apikey=${bkey}" \
|
|
2>/dev/null | jq -r '.general.enabled_providers[]? // empty' 2>/dev/null || true)
|
|
fi
|
|
fi
|
|
else
|
|
prompt="Bazarr providers (optional)> "
|
|
header="Enter/TAB=toggle Ctrl-O=confirm Esc=skip"
|
|
fi
|
|
fzf_multi_select "$formatted" \
|
|
"$prompt" \
|
|
"$header" \
|
|
SELECTED_SUBTITLE_PROVIDERS \
|
|
"provider" \
|
|
"$preselect_names" || true
|
|
ui_picker_done "providers"
|
|
}
|