- Reset master to upstream/main (16,697 commits) - Overlay 2,271 local-only files (skills, tools, workspace, configs, apps) - Restore IDENTITY.md and USER.md templates - Build verified, gateway running, Discord working Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
211 lines
6.7 KiB
Bash
211 lines
6.7 KiB
Bash
#!/usr/bin/env bash
|
|
# price-monitor.sh - Pris-overvagning for produkter (homeshop.dk + pricerunner.dk)
|
|
# Brug: bash price-monitor.sh <produkt-id>
|
|
# Eksempel: bash price-monitor.sh dewalt-dcd996nt
|
|
|
|
set -euo pipefail
|
|
|
|
PRODUCT_ID="${1:-dewalt-dcd996nt}"
|
|
DB_FILE="/home/alex/clawd/data/prices/${PRODUCT_ID}.json"
|
|
|
|
if [[ ! -f "$DB_FILE" ]]; then
|
|
echo "FEJL: Database ikke fundet: $DB_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Les konfiguration fra database
|
|
URL=$(python3 -c "import json,sys; d=json.load(open('$DB_FILE')); print(d['url'])")
|
|
BASELINE=$(python3 -c "import json,sys; d=json.load(open('$DB_FILE')); print(d['baseline_price'])")
|
|
THRESHOLD=$(python3 -c "import json,sys; d=json.load(open('$DB_FILE')); print(d['alert_threshold_pct'])")
|
|
BUY_UNDER=$(python3 -c "import json,sys; d=json.load(open('$DB_FILE')); print(d['buy_under_dkk'])")
|
|
PRODUCT=$(python3 -c "import json,sys; d=json.load(open('$DB_FILE')); print(d['product'])")
|
|
PR_SEARCH=$(python3 -c "import json,sys; d=json.load(open('$DB_FILE')); print(d.get('pricerunner_search', ''))")
|
|
|
|
echo "=== Pris-check: $PRODUCT ==="
|
|
echo "Baseline: ${BASELINE} DKK | Threshold: ${THRESHOLD}% | Kobsgraense: ${BUY_UNDER} DKK"
|
|
echo ""
|
|
|
|
# -------------------------------------------------------
|
|
# Funktion: Scrape homeshop.dk
|
|
# -------------------------------------------------------
|
|
scrape_homeshop() {
|
|
python3 /home/alex/clawd/tools/scrape-price.py "$URL" 2>/dev/null
|
|
}
|
|
|
|
# -------------------------------------------------------
|
|
# Funktion: Scrape pricerunner.dk sogeside
|
|
# -------------------------------------------------------
|
|
scrape_pricerunner() {
|
|
local search_url="$1"
|
|
if [[ -z "$search_url" ]]; then
|
|
echo "N/A"
|
|
return
|
|
fi
|
|
python3 /home/alex/clawd/tools/scrape-price.py "$search_url" 2>/dev/null
|
|
}
|
|
|
|
# -------------------------------------------------------
|
|
# Hent priser fra begge kilder
|
|
# -------------------------------------------------------
|
|
echo "Henter pris fra homeshop.dk..."
|
|
HOMESHOP_PRICE=$(scrape_homeshop)
|
|
|
|
echo "Henter pris fra pricerunner.dk..."
|
|
PR_PRICE=$(scrape_pricerunner "$PR_SEARCH")
|
|
|
|
# -------------------------------------------------------
|
|
# Find laveste pris
|
|
# -------------------------------------------------------
|
|
BEST_SOURCE="homeshop.dk"
|
|
BEST_PRICE_RAW="$HOMESHOP_PRICE"
|
|
|
|
if [[ "$HOMESHOP_PRICE" == "ERROR" || -z "$HOMESHOP_PRICE" ]]; then
|
|
echo "ADVARSEL: Kunne ikke hente pris fra homeshop.dk" >&2
|
|
HOMESHOP_PRICE="N/A"
|
|
fi
|
|
|
|
CURRENT_INT=""
|
|
if [[ "$HOMESHOP_PRICE" != "N/A" && "$HOMESHOP_PRICE" != "ERROR" ]]; then
|
|
CURRENT_INT=$(python3 -c "print(int(float('$HOMESHOP_PRICE')))" 2>/dev/null || echo "")
|
|
fi
|
|
|
|
# Sammenlign med PriceRunner hvis tilgaengelig
|
|
PR_INT=""
|
|
if [[ "$PR_PRICE" != "N/A" && -n "$PR_PRICE" ]]; then
|
|
PR_INT=$(python3 -c "print(int(float('$PR_PRICE')))" 2>/dev/null || echo "")
|
|
fi
|
|
|
|
# Vaelg laveste pris som "best"
|
|
if [[ -n "$PR_INT" && -n "$CURRENT_INT" ]]; then
|
|
BEST=$(python3 -c "
|
|
hs = $CURRENT_INT
|
|
pr = $PR_INT
|
|
if pr < hs:
|
|
print(f'pr:{pr}')
|
|
else:
|
|
print(f'hs:{hs}')
|
|
")
|
|
if [[ "$BEST" == pr:* ]]; then
|
|
BEST_SOURCE="pricerunner.dk"
|
|
BEST_PRICE_RAW="$PR_INT"
|
|
CURRENT_INT="$PR_INT"
|
|
fi
|
|
elif [[ -n "$PR_INT" && -z "$CURRENT_INT" ]]; then
|
|
BEST_SOURCE="pricerunner.dk"
|
|
CURRENT_INT="$PR_INT"
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- Priser ---"
|
|
[[ "$HOMESHOP_PRICE" != "N/A" && "$HOMESHOP_PRICE" != "ERROR" ]] && \
|
|
echo " homeshop.dk: ${HOMESHOP_PRICE%.*} DKK"
|
|
[[ -n "$PR_INT" ]] && echo " pricerunner.dk: ${PR_INT} DKK (laveste fundet)"
|
|
[[ -z "$PR_INT" ]] && echo " pricerunner.dk: ikke tilgaengelig"
|
|
echo ""
|
|
|
|
if [[ -z "$CURRENT_INT" ]]; then
|
|
echo "FEJL: Ingen priser kunne hentes fra nogen kilde" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Bedste pris: ${CURRENT_INT} DKK (fra ${BEST_SOURCE})"
|
|
|
|
# Beregn aendring fra baseline
|
|
PRICE_CHANGE=$(python3 -c "
|
|
baseline = float($BASELINE)
|
|
current = float($CURRENT_INT)
|
|
pct = ((baseline - current) / baseline) * 100
|
|
print(f'{pct:.1f}')
|
|
")
|
|
echo "Aendring fra baseline (${BASELINE} DKK): ${PRICE_CHANGE}%"
|
|
|
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
# -------------------------------------------------------
|
|
# Gem priser i database
|
|
# -------------------------------------------------------
|
|
python3 -c "
|
|
import json
|
|
|
|
db_file = '$DB_FILE'
|
|
with open(db_file, 'r') as f:
|
|
data = json.load(f)
|
|
|
|
entries = []
|
|
if '$HOMESHOP_PRICE' not in ('N/A', 'ERROR', ''):
|
|
entries.append({
|
|
'price': int(float('$HOMESHOP_PRICE')),
|
|
'currency': 'DKK',
|
|
'timestamp': '$TIMESTAMP',
|
|
'source': 'homeshop.dk'
|
|
})
|
|
if '$PR_INT' not in ('', 'N/A'):
|
|
entries.append({
|
|
'price': int('$PR_INT'),
|
|
'currency': 'DKK',
|
|
'timestamp': '$TIMESTAMP',
|
|
'source': 'pricerunner.dk'
|
|
})
|
|
|
|
data['prices'].extend(entries)
|
|
|
|
# Hold kun de 90 seneste priser
|
|
if len(data['prices']) > 90:
|
|
data['prices'] = data['prices'][-90:]
|
|
|
|
data['last_best_price'] = $CURRENT_INT
|
|
data['last_best_source'] = '$BEST_SOURCE'
|
|
data['last_checked'] = '$TIMESTAMP'
|
|
|
|
with open(db_file, 'w') as f:
|
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
|
|
print(f'Priser gemt ({len(entries)} poster).')
|
|
"
|
|
|
|
# -------------------------------------------------------
|
|
# Alert-logik: baseret pa bedste pris
|
|
# -------------------------------------------------------
|
|
BELOW_BUY_UNDER=$(python3 -c "print('yes' if $CURRENT_INT <= $BUY_UNDER else 'no')")
|
|
TRIGGER_ALERT=$(python3 -c "
|
|
baseline = float($BASELINE)
|
|
current = float($CURRENT_INT)
|
|
threshold = float($THRESHOLD)
|
|
pct_drop = ((baseline - current) / baseline) * 100
|
|
print('yes' if pct_drop >= threshold else 'no')
|
|
")
|
|
|
|
echo ""
|
|
if [[ "$TRIGGER_ALERT" == "yes" ]]; then
|
|
PCT_DROP=$(python3 -c "
|
|
baseline = float($BASELINE)
|
|
current = float($CURRENT_INT)
|
|
pct = ((baseline - current) / baseline) * 100
|
|
print(f'{pct:.0f}')
|
|
")
|
|
echo "ALERT: Pris faldet >=${THRESHOLD}% fra baseline!"
|
|
PR_LINE=""
|
|
[[ -n "$PR_INT" ]] && PR_LINE="
|
|
PriceRunner: ${PR_INT} DKK"
|
|
ALERT_MSG="Prisfald! ${PRODUCT}: ${CURRENT_INT} DKK (var ${BASELINE} DKK, -${PCT_DROP}%) [${BEST_SOURCE}]${PR_LINE}
|
|
${URL}"
|
|
echo "Sender Discord alert..."
|
|
openclaw message send --channel discord --target "1466863360099356763" \
|
|
-m "$ALERT_MSG" 2>/dev/null || echo "ADVARSEL: Discord alert fejlede"
|
|
elif [[ "$BELOW_BUY_UNDER" == "yes" && "$CURRENT_INT" -lt "$BASELINE" ]]; then
|
|
echo "INFO: Pris under kobsgraense (${CURRENT_INT} <= ${BUY_UNDER} DKK)"
|
|
PR_LINE=""
|
|
[[ -n "$PR_INT" ]] && PR_LINE="
|
|
PriceRunner: ${PR_INT} DKK"
|
|
ALERT_MSG="KOB NU! ${PRODUCT}: ${CURRENT_INT} DKK (under kobsgraensen ${BUY_UNDER} DKK!) [${BEST_SOURCE}]${PR_LINE}
|
|
${URL}"
|
|
echo "Sender Discord alert (kobsgraense)..."
|
|
openclaw message send --channel discord --target "1466863360099356763" \
|
|
-m "$ALERT_MSG" 2>/dev/null || echo "ADVARSEL: Discord alert fejlede"
|
|
else
|
|
echo "OK: Ingen alert - bedste pris stabil (${CURRENT_INT} DKK, aendring: ${PRICE_CHANGE}%)"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Pris-check fuldfert: $(date)"
|