Files
EpicNext-Cms/scripts/catalog-sync.sh
T
openhands 77a0d1010c
CI / check (push) Successful in 47s
CI / tests-integration (push) Successful in 2m36s
CI / tests-unit (push) Successful in 2m52s
CI / tests-ui (push) Successful in 3m28s
CI / preflight (push) Skipped
CI / tests-quality (push) Successful in 1m30s
CI / tests-imaging-browsers (push) Successful in 2m47s
CI / deploy (push) Successful in 3m21s
feat(catalog-sync): report sources that can only be compared, not imported
The Habbo presets (and the other furnidata-only sources) carry no
`nitroBaseUrl`, so the clone importer's `${nitroBaseUrl}/${classname}.nitro`
download can never work against them — an `--import-new` run would have failed
for every single item instead of saying why. The SWF path that does work against
`images.habbo.com` lives in `furni-import.ts`, not in the clone importer this
CLI uses.

`stepImportNew` now skips those sources up-front, lists them under
"compared only" in the summary, and the menu prints the same warning when an
import step is chosen with such a source, pointing at the sources that do serve
bundles. Verified: Habbo (COM) reports compare-only, while Habboon and
VirtualCity list real candidates.
2026-10-15 20:18:34 +02:00

211 lines
6.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Update the hotel catalog from a clone source (Habbo, retros, …).
#
# Two ways to use it:
# ./scripts/catalog-sync.sh interactive menu
# ./scripts/catalog-sync.sh --all --sql same flags as the CLI, no menu
#
# The menu asks which sites to compare (Habbo is the preset) and what may be
# written. An audit is read-only; only a repair option touches the catalog, and
# applying the generated catalog SQL always needs its own choice.
cd "$(dirname "$0")/.." || exit 1
CLI="scripts/catalog-sync.ts"
# The react-server export condition resolves `server-only` to its no-op build,
# exactly like `pnpm jobs:worker` — the audit engine imports server modules.
RUN=(node --conditions=react-server --import tsx "$CLI")
# Any flag passed straight through means "no menu", so the same entry point
# works for cron/CI where only the flag path is safe.
if [ $# -gt 0 ]; then
exec "${RUN[@]}" "$@"
fi
if [ ! -t 0 ]; then
echo "Not a terminal. Use the non-interactive form, e.g.:" >&2
echo " $0 --all --sources=default-habbo-com" >&2
exit 1
fi
echo "=== Catalog sync ==="
echo
# ── Step 1: which sites to compare ───────────────────────────────────────────
# Ask the CLI for the configured sources so the menu always matches the live
# `clone_sources` setting (and its defaults) instead of a hardcoded list.
mapfile -t SOURCE_ROWS < <("${RUN[@]}" --list-sources --tsv)
if [ "${#SOURCE_ROWS[@]}" -eq 0 ]; then
echo "No clone sources are configured." >&2
exit 1
fi
IDS=()
NAMES=()
echo "Compare this catalog against:"
for i in "${!SOURCE_ROWS[@]}"; do
IFS=$'\t' read -r SOURCE_ID SOURCE_NAME _SOURCE_URL <<<"${SOURCE_ROWS[$i]}"
IDS+=("$SOURCE_ID")
NAMES+=("$SOURCE_NAME")
printf " %2d) %-24s %s\n" "$((i + 1))" "$SOURCE_NAME" "$SOURCE_ID"
done
echo
echo " a) all sources above"
echo " Enter) preset: ${NAMES[0]}"
read -r -p "Sources [a, 1-${#IDS[@]}, Enter]: " SOURCE_CHOICE
SELECTED_FLAG=""
case "$SOURCE_CHOICE" in
"" | "Habbo" | "habbo" | "habbo.com")
# Habbo is the first default preset (Habbo (COM)).
SELECTED_FLAG="--sources=${IDS[0]}"
SOURCE_LABEL="${NAMES[0]}"
;;
a | A | all)
SOURCE_LABEL="all sources"
;;
*)
SELECTED_IDS=()
IFS=', ' read -r -a CHOICES <<<"$SOURCE_CHOICE"
for CHOICE in "${CHOICES[@]}"; do
[ -z "$CHOICE" ] && continue
case "$CHOICE" in
*[!0-9]* | "")
echo "Invalid choice: '$CHOICE'" >&2
exit 1
;;
*)
if [ "$CHOICE" -lt 1 ] || [ "$CHOICE" -gt "${#IDS[@]}" ]; then
echo "Out of range: $CHOICE" >&2
exit 1
fi
SELECTED_IDS+=("${IDS[$((CHOICE - 1))]}")
;;
esac
done
if [ "${#SELECTED_IDS[@]}" -eq 0 ]; then
echo "No sources selected." >&2
exit 1
fi
SELECTED_FLAG="--sources=$(IFS=,; echo "${SELECTED_IDS[*]}")"
SOURCE_LABEL="${#SELECTED_IDS[@]} source(s)"
;;
esac
# ── Step 2: what may be changed ──────────────────────────────────────────────
cat <<EOF
What should be done? (comparing with: $SOURCE_LABEL)
1) Audit only — nothing is written
2) Repair furniture icons + nitro bundles
3) 2 + FurnitureData.json + catalog structure
4) 3 + generate the catalog SQL (saved, not applied)
5) 4 + APPLY the catalog SQL to the catalog
6) 5 + import the furniture the source has and this hotel lacks
(downloads icon + bundle, adds FurnitureData.json, creates the catalog
page and the offer for every new item)
7) 6 + reconcile FurnitureData.json / sprite ids / offer ids
8) 7 + align items_base ids and rename legacy .nitro bundles (heaviest)
d) Dry run — only report what 6 would import
q) Cancel
EOF
read -r -p "Action [1]: " ACTION_CHOICE
ACTION_CHOICE="${ACTION_CHOICE:-1}"
ACTIONS=(audit)
CONFIRM=""
# Empty for the audit-only branch on purpose: no repair flags are passed.
ACTION_FLAGS=()
# Prints a warning when the chosen source only serves furnidata (no .nitro
# bundles), so the import step would find nothing to download.
warn_compare_only_sources() {
case "$SOURCE_LABEL" in
"Habbo (COM)" | "Habbo (NL)" | "Habbo (DE)" | "Habbo (FR)" | \
"Habbo (ES)" | "Habbo (FI)" | "Habbo (BR)" | "Habbo (TR)" | \
"Habbo (IT - GitHub mirror)" | "YabboHotel" | "Wibbo" | "Hubba.cc")
echo
echo "Note: ${SOURCE_LABEL} only serves furnidata — there are no .nitro"
echo "bundles to import, so the import step can only COMPARE."
echo "To actually import furniture, pick a source that serves bundles"
echo "(Habblet City, VirtualCity, SodaStudios, Habboon, Leet, Hubbly) or 'a'."
;;
esac
}
case "$ACTION_CHOICE" in
1)
ACTIONS=(audit "[nothing written]")
;;
2)
ACTIONS=(audit "icons + nitro bundles downloaded")
ACTION_FLAGS=(--icons --nitros)
;;
3)
ACTIONS=(audit "icons + nitro bundles + FurnitureData.json + structure")
ACTION_FLAGS=(--all)
;;
4)
ACTIONS=(audit "everything + catalog SQL generated")
ACTION_FLAGS=(--all --sql)
;;
5)
ACTIONS=(audit "everything + catalog SQL APPLIED")
ACTION_FLAGS=(--all --sql --apply-sql)
CONFIRM="This writes to the catalog. Continue? [y/N]: "
;;
6)
ACTIONS=(audit "everything + catalog SQL + import new furniture")
ACTION_FLAGS=(--all --sql --apply-sql --import-new)
CONFIRM="This writes to the catalog AND imports every missing item. Continue? [y/N]: "
;;
7)
ACTIONS=(audit "everything + import + reconcile")
ACTION_FLAGS=(
--all --sql --apply-sql --import-new --reconcile --fix-sprite-ids
--rebuild-offers
)
CONFIRM="This writes to the catalog AND imports every missing item. Continue? [y/N]: "
;;
8)
ACTIONS=(audit "everything + import + reconcile + id alignment + rename")
ACTION_FLAGS=(
--all --sql --apply-sql --import-new --reconcile --fix-sprite-ids
--rebuild-offers --align-ids --rename-bundles --apply
)
read -r -p " This rewrites items_base ids and renames bundle files. Type RUN to confirm: " REPLY
if [ "$REPLY" != "RUN" ]; then
echo "Cancelled."
exit 0
fi
;;
d | D)
ACTIONS=(audit "report only — nothing is imported")
ACTION_FLAGS=(--import-new --dry-run)
;;
q | Q)
echo "Cancelled."
exit 0
;;
*)
echo "Invalid action: '$ACTION_CHOICE'" >&2
exit 1
;;
esac
echo
printf 'Running: %s against %s\n' "${ACTIONS[1]}" "$SOURCE_LABEL"
case "$ACTION_FLAGS" in
*--import-new*) warn_compare_only_sources ;;
esac
if [ -n "$CONFIRM" ]; then
read -r -p "$CONFIRM" REPLY
if [ "$REPLY" != "y" ] && [ "$REPLY" != "Y" ]; then
echo "Cancelled."
exit 0
fi
fi
echo
# shellcheck disable=SC2086
"${RUN[@]}" $SELECTED_FLAG "${ACTION_FLAGS[@]}"