home-server
sort.sh
#!/bin/sh
DOWNLOADS=/downloads
INGEST=/ingest
SENT=/downloads/.cwa-sent
AUDIOBOOKS=$DOWNLOADS/audiobooks
ABS=http://172.21.0.1:13378
ABS_USER=CHANGE_ME
ABS_PASS="CHANGE_ME"
AUDIO_LIB=d93406c2-ae51-45a3-978f-a06ff63f8c2f
mkdir -p "$AUDIOBOOKS" "$SENT"
rm -f /tmp/moved_ebooks /tmp/moved_audio
# Fresh ABS token on every run — avoids stale JWT from file
TOKEN=$(wget -qO- --header="Content-Type: application/json" \
--post-data="{\"username\":\"$ABS_USER\",\"password\":\"$ABS_PASS\"}" \
"$ABS/login" 2>/dev/null | python3 -c "import json,sys; print(json.load(sys.stdin)['user']['accessToken'])" 2>/dev/null)
[ -z "$TOKEN" ] && echo "[sort] Warning: could not get ABS token, metadata step will be skipped"
# Process EPUBs and PDFs — quote path exclusions to prevent glob expansion.
# All prose ebooks go to CWA's ingest folder; CWA auto-converts/organizes and
# then REMOVES the file from ingest once done (it's a watch-and-consume
# folder, not persistent storage). kobodl also has no way to skip
# re-downloading a title on every 300s cycle, and re-writes over our symlink
# each time -- so "does it already exist in ingest" is never a reliable
# skip-check (ingest is usually empty because CWA just ate it). Track what's
# already been sent in $SENT instead, which nothing else touches.
find "$DOWNLOADS" \( -name '*.epub' -o -name '*.pdf' \) \
! -path "${AUDIOBOOKS}/*" ! -path "${SENT}/*" \
! -type l | while IFS= read -r f; do
fname=$(basename "$f")
if [ ! -f "$SENT/$fname" ]; then
cp "$f" "$INGEST/$fname" && echo "[ebook] -> ingest: $fname"
cp "$f" "$SENT/$fname"
echo "$fname" >> /tmp/moved_ebooks
fi
# Symlink back so kobodl's re-download lands somewhere harmless-ish;
# this is belt-and-suspenders since the real skip-check is $SENT now.
ln -sfn "$SENT/$fname" "$f" 2>/dev/null
done
# Process audio — use while loop (not xargs) to handle spaces in filenames
find "$DOWNLOADS" \( -name '*.mp3' -o -name '*.m4b' -o -name '*.m4a' \) \
! -path "${AUDIOBOOKS}/*" \
! -type l | while IFS= read -r af; do
dirname "$af"
done | sort -u | while IFS= read -r audiodir; do
[ -L "$audiodir" ] && continue
title=$(basename "$audiodir")
author=$(basename "$(dirname "$audiodir")")
dest="$AUDIOBOOKS/$author/$title"
[ "$audiodir" = "$dest" ] && continue
mkdir -p "$AUDIOBOOKS/$author"
mv "$audiodir" "$dest" && echo "[audiobook] $author/$title"
echo "$author/$title" >> /tmp/moved_audio
mkdir -p "$(dirname "$audiodir")"
ln -sfn "$dest" "$audiodir"
done
# Companion: find any epub for the same title in $SENT (the persistent
# ebook store) -- NOT $INGEST, which CWA empties out after processing.
for audiobook_dir in "$AUDIOBOOKS"/*/*; do
[ -d "$audiobook_dir" ] || continue
title=$(basename "$audiobook_dir")
ebook_file=$(find "$SENT" -maxdepth 1 -iname "*${title}*.epub" 2>/dev/null | head -1)
[ -z "$ebook_file" ] && continue
ebook_fname=$(basename "$ebook_file")
if [ ! -f "$audiobook_dir/$ebook_fname" ]; then
cp "$ebook_file" "$audiobook_dir/"
echo "[companion] $title"
echo "$title" >> /tmp/moved_audio
fi
done
HAS_EBOOKS=0; HAS_AUDIO=0
[ -s /tmp/moved_ebooks ] && HAS_EBOOKS=1
[ -s /tmp/moved_audio ] && HAS_AUDIO=1
[ "$HAS_EBOOKS$HAS_AUDIO" = "00" ] && exit 0
[ -z "$TOKEN" ] && echo "[sort] No ABS token, skipping ABS scan" && exit 0
abs_post() { wget -qO- --header="Authorization: Bearer $TOKEN" --header="Content-Type: application/json" --post-data="$2" "$ABS$1" 2>/dev/null; }
abs_get() { wget -qO- --header="Authorization: Bearer $TOKEN" "$ABS$1" 2>/dev/null; }
[ "$HAS_AUDIO" = "1" ] && echo "[sort] Scanning audiobooks..." && abs_post "/api/libraries/$AUDIO_LIB/scan" "{}" > /dev/null
echo "[sort] Waiting 20s for ABS to index..."
sleep 20
fetch_metadata() {
LIB=$1 PROV=$2
abs_get "/api/libraries/$LIB/items?limit=500" | python3 -c "
import sys,json
try:
r=json.load(sys.stdin)
for item in r.get('results',[]):
m=item.get('media',{}).get('metadata',{})
if not item.get('coverPath') or not m.get('description'):
t=m.get('title','?')
a=m.get('authorName',m.get('authors','?'))
print(item['id']+'|'+t+'|'+str(a))
except: pass
" 2>/dev/null | while IFS='|' read -r ID TITLE AUTHOR; do
[ -z "$ID" ] && continue
echo "Matching: $TITLE"
abs_post "/api/items/$ID/match" "{\"provider\":\"$PROV\",\"updateDetails\":true}" > /dev/null
sleep 3
done
}
[ "$HAS_AUDIO" = "1" ] && fetch_metadata "$AUDIO_LIB" audible
rm -f /tmp/moved_ebooks /tmp/moved_audio
echo "[sort] Done."