# Find Refurbished Apple Product

Watches Apple's refurbished Mac listing every 5 minutes and sends an ntfy ping when a new target product appears.

Default target:

- Refurbished Mac Studio with 64GB RAM or better

Each user should configure their own `.env.local` so notification topics and target criteria do not collide.

```bash
cp .env.example .env.local
```

Edit `.env.local` and set a unique `NTFY_TOPIC`, plus any product criteria you want to watch.

## Run

```bash
python3 refurbished_watcher.py
```

The first run creates `.refurbished_watch_state.json` as a baseline and does not notify. Later runs notify only for products that were not present in the previous check.

The watcher records compact per-poll observations to `.refurbished_poll_history.jsonl` and full inventory-change snapshots to `.refurbished_inventory_history.jsonl`. This lets you learn when Apple tends to update inventory instead of guessing, without writing the full product list on every no-change poll. When any inventory change or target hit is observed, the watcher temporarily switches from `INTERVAL_SECONDS` to `HOT_INTERVAL_SECONDS` for `HOT_DURATION_SECONDS`.

For a one-time test without sending ntfy notifications:

```bash
python3 refurbished_watcher.py --once --dry-run
```

To notify for every matching product on the first run:

```bash
python3 refurbished_watcher.py --once --notify-on-first-run
```

## Add matching products to Apple's bag

Cart automation is optional. The normal watcher uses only the Python standard library, but cart automation needs Playwright so it can drive a local Chromium browser.

```bash
python3 -m pip install playwright
python3 -m playwright install chromium
python3 refurbished_watcher.py --once --notify-on-first-run --add-to-cart --cart-keep-open
```

When `--add-to-cart` is enabled, each newly found matching product is opened in a persistent local browser profile and the script clicks Apple's "Add to Bag" button. If several matching refurbished units appear with different product IDs/specs, the watcher attempts one add per matching product. Use `--cart-keep-open` for one-shot runs when you want the Apple bag to stay open for manual review or checkout. The default profile directory is `.apple_cart_browser_profile/`, which is ignored by git because it can contain browser cookies and session data.

On startup, `--add-to-cart` checks whether Playwright can launch Chromium. If Playwright is missing, the script prints the install commands. If Playwright is installed but Chromium is missing, the script asks whether to install Chromium with `python3 -m playwright install chromium`. If cart setup still cannot launch Chromium, the script asks whether to continue with only the watcher and ntfy notifications. This keeps you from accidentally running with broken cart automation unless you explicitly choose notifier-only mode.

For a safe live test against an in-stock item, temporarily set `APPLE_REFURB_URL` to a page/listing whose criteria match a low-cost available refurbished product, run with `--once --notify-on-first-run --add-to-cart --cart-keep-open`, confirm the browser bag contains the item, and remove it from the bag before checkout. Do not run cart tests in headless mode until the visible flow has been confirmed.

Example test run against one in-stock refurbished MacBook Air:

```bash
python3 refurbished_watcher.py --once --notify-on-first-run --add-to-cart --cart-keep-open --no-notify --target-title-contains "macbook air" --min-ram-gb 16 --max-products 1
```

## Configuration

Configuration is read from shell environment variables and, if present, `.env.local`. Shell environment variables take precedence over `.env.local`.

Copy `.env.example` to `.env.local` for a per-machine setup:

```bash
cp .env.example .env.local
```

- `APPLE_REFURB_URL`: Apple listing URL. Defaults to `https://www.apple.com/shop/refurbished/mac`.
- `NTFY_SERVER`: ntfy server. Defaults to `https://ntfy.sh`.
- `NTFY_TOPIC`: ntfy topic. Required for notification runs. Use a unique topic per person/instance.
- `INTERVAL_SECONDS`: polling interval. Defaults to `300`.
- `HOT_INTERVAL_SECONDS`: faster polling interval after an inventory change or target hit. Defaults to `60`.
- `HOT_DURATION_SECONDS`: how long hot polling remains active after a change or hit. Defaults to `1800`.
- `STATE_FILE`: local state file. Defaults to `.refurbished_watch_state.json`.
- `INVENTORY_HISTORY_FILE`: JSONL inventory-change history. Defaults to `.refurbished_inventory_history.jsonl`.
- `INVENTORY_STATE_FILE`: local inventory fingerprint state. Defaults to `.refurbished_inventory_state.json`.
- `POLL_HISTORY_FILE`: compact JSONL record for every successful poll. Defaults to `.refurbished_poll_history.jsonl`; set empty to disable.
- `REQUEST_TIMEOUT`: HTTP timeout in seconds. Defaults to `30`.
- `TARGET_TITLE_CONTAINS`: required title text. Defaults to `mac studio`.
- `MIN_RAM_GB`: minimum RAM in GB. Defaults to `64`.
- `MAX_PRODUCTS`: maximum new products to handle in one check. Defaults to `0` for no limit.
- `ADD_TO_CART`: set to `1`, `true`, or `yes` to enable cart automation.
- `CART_PROFILE`: persistent browser profile directory. Defaults to `.apple_cart_browser_profile`.
- `CART_HEADLESS`: set to `1`, `true`, or `yes` to run cart automation headlessly.
- `CART_KEEP_OPEN`: set to `1`, `true`, or `yes` to keep the cart browser open after adding products.

Each notification includes the product title, price, product URL, and RAM/storage when those details can be found on the listing or product detail page.

## Run as a macOS service

The repo includes `com.jameslopez.refurbished-watcher.plist`, a launchd agent template for this local checkout. It runs the watcher with `.venv/bin/python`, loads `.env.local`, restarts if it exits, and writes logs to:

- `refurbished-watcher.out.log`
- `refurbished-watcher.err.log`

Install and start it:

```bash
mkdir -p ~/Library/LaunchAgents
cp com.jameslopez.refurbished-watcher.plist ~/Library/LaunchAgents/
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.jameslopez.refurbished-watcher.plist
launchctl kickstart -k gui/$(id -u)/com.jameslopez.refurbished-watcher
```

Check status:

```bash
launchctl print gui/$(id -u)/com.jameslopez.refurbished-watcher
tail -f refurbished-watcher.out.log refurbished-watcher.err.log
```

Stop it:

```bash
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.jameslopez.refurbished-watcher.plist
```

## Portability and sharing

The tracked repo has no personal data or secrets. It contains public Apple URLs, placeholder config, tests, and source code. Local state files are ignored:

- `.env.local` stores each person's real topic and criteria.
- `.refurbished_watch_state.json` stores seen product keys for this machine.
- `.refurbished_poll_history.jsonl`, `.refurbished_inventory_history.jsonl`, and `.refurbished_inventory_state.json` store local inventory timing data.
- `.apple_cart_browser_profile/` stores local browser session data for cart automation.
- `__pycache__/`, test caches, and temporary state files are ignored.

Before adding a collaborator, keep the repo private unless you are comfortable sharing the product-watching logic. Each user should choose their own `NTFY_TOPIC`, customize target criteria in `.env.local`, and avoid committing local state or browser profile directories.
