尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Track OLX.ro listings, price drops and market-relative deals in a self-hosted mobile dashboard.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Track products on OLX.ro and surface genuinely good deals — on a self-hosted, mobile-friendly dashboard you can reach from your phone.
It periodically pulls listings for the searches you configure (e.g. used iPhone 14, Samsung Galaxy Z Fold 6), diffs them against a local database to detect new listings and price changes, scores each one against the market, and shows the results — including price-drop history and per-listing exclusions.
Hide seen clears them from the view
entirely once a search has been worked through.https://www.olx.ro/api/v1/offers/filter_enum_model[0]=iphone_14, filter_enum_state[0]=used,
filter_float_price:from / :to, region_id.category_id=0 (all categories) to avoid a mismatched
category hiding your results. The app defaults to this.python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txt
umask 077
printf 'ADMIN_TOKEN=%s\nPUBLIC_BASE_URL=http://127.0.0.1:8000\n' \
"$(openssl rand -hex 32)" > .env
set -a
. ./.env
set +a
# 1) define what to track (or use the dashboard's Manage tab)
# see searches.yaml for the format
# 2) run one sync cycle
python run.py
# 3) launch the dashboard
python -m olxdeals.dashboard --host 127.0.0.1 --port 8000
# open http://127.0.0.1:8000/
Use Python 3.12 or newer, openssl, and an Internet connection for listing sync.
The example binds only to loopback. New browsers need a device invitation.
The commands create a new ignored .env; do not overwrite existing private
configuration. In another terminal in the same checkout, load that file:
set -a
. ./.env
set +a
curl --fail http://127.0.0.1:8000/api/admin/invites \
-H "X-Admin-Token: $ADMIN_TOKEN" -H 'Content-Type: application/json' \
--data '{"label":"Demo device"}'
Open the returned invitation URL. For access from your phone, configure HTTPS and
a private/protected listener; do not expose an unconfigured development port.
The current backend expects the secret in X-Admin-Token, not a legacy X-Admin
marker. Set ADMIN_TOKEN in the private container environment too: missing means
admin access is denied. Project overview.
python -m olxdeals.discover "iphone 15 pro"
Prints the category ids and model keys that actually appear in real listings, with counts. The dashboard's Manage tab has the same finder as tappable chips.
When OLX's own filters are too coarse, e.g. RAM only goes up to a "> 16 GB" bucket, a search can filter on the listing text after fetching. All patterns are case-insensitive regexes:
- key: mac_mini_24gb
category_id: 2419
query: mac mini
title_match: 'mac\s*-?\s*mini' # must match the title
require: # each must match title + description
- '\bm[1-9](?!\d)'
- '(?<![\d.])(24|32|48|64)\s*gb(?!\s*ssd)'
exclude: 'intel' # must not match title + description
notify: new # push every new listing, not only deals
notify: new is for thin searches: deal scoring needs at least five prices to
judge, so a search with a handful of listings would otherwise never push. The
Manage tab doesn't show these fields, but saving a search there keeps them.
deploy/ runs the dashboard as a rootless container with an hourly
sync, surviving logout and reboot. No root, no system-wide Python.
podman build -t olx-deals:latest -f deploy/Containerfile .
loginctl enable-linger "$USER" # run without being logged in
install -d -m 700 ~/.config/olx-deals
cat > ~/.config/olx-deals/olx.env <<'ENV'
PUBLIC_BASE_URL=https://olx.yourdomain.com
NTFY_URL=https://ntfy.sh/pick-something-unguessable
GEMINI_API_KEY=
ENV
chmod 600 ~/.config/olx-deals/olx.env
cp deploy/quadlet/olx-deals.container ~/.config/containers/systemd/
cp deploy/quadlet/olx-sync.{service,timer} ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user start olx-deals.service
systemctl --user enable --now olx-sync.timer
The dashboard listens on 127.0.0.1:8092; put a reverse proxy in front of it
if you want it reachable from elsewhere.
Everything mutable lives in the named volume olx-deals-data: olxdeals.db,
searches.yaml (the Manage tab writes to it) and vapid_key.pem. The VAPID
key must survive rebuilds — regenerating it silently invalidates every push
subscription already granted, with no error anywhere.
Access is managed through single-use device invites (via the shared
pwa-invite-console or the ./admin.sh CLI):
HttpOnly cookie.pwa-invite-console PWA or
via ./admin.sh invite "My Phone".NTFY_URL lives in the same env file — see Notifications.deploy/import-data.sh loads a previous dataset into the volume:
tar czf olx-data.tgz olxdeals.db searches.yaml vapid_key.pem # on the old host
./deploy/import-data.sh <dir-with-those-files> # on the new one
It stops the service, copies through podman cp so ownership lands right, and
starts it again. Bring vapid_key.pem — see above.
Two channels, carrying different things:
The sync runs hourly, so an unconditional status message meant 24 notifications a day, of which roughly 24 said everything was fine. That trains you to swipe the topic away, which is precisely when a real failure goes unread.
| Variable | Default | Effect |
|---|---|---|
NTFY_URL | https://ntfy.sh/monitoring | Where operational alerts go. Change it — the default is a public topic anyone can read and post to. |
NTFY_REPEAT_HOURS | 6 | While a failure persists, re-notify at most this often. A problem lasting all day should not go quiet after its first mention. |
NTFY_HEARTBEAT_HOURS | 0 (off) | Set to e.g. 24 for one liveness ping a day. Worth considering: with problems-only reporting, silence cannot distinguish "healthy" from "the timer stopped firing". |
State lives in the meta table (ntfy_state, ntfy_last_fail,
ntfy_last_beat), so a failure and its recovery are detected as transitions
across separate sync runs rather than recomputed from scratch each time.
olxdeals/
fetcher.py OLX API client (paginated, polite) + SearchSpec
store.py SQLite persistence, diffing, price history, exclusions
scorer.py currency normalise, junk filter, deal/suspicious scoring
discover.py find category ids & model keys from real listings
config.py read/write searches.yaml
dashboard.py zero-dependency web UI (Deals / Drops / Manage)
analyzer.py optional per-listing LLM verdict (Gemini Flash 3.8)
push.py self-served VAPID web push
run.py one sync cycle (used by the timer)
searches.yaml your tracked searches
deploy/ Containerfile, quadlet units, data import
EUR→RON rate is a constant in scorer.py — update it occasionally.olxdeals.db is git-ignored; it's rebuilt by running the sync.llm_analysis.cost_usd records what each
one cost. Leave GEMINI_API_KEY empty and the rest of the app works
unchanged; only that button fails.Use this repository’s Issues for reproducible bugs and focused feature requests. Include the version, relevant runtime/browser, expected behavior and a minimal synthetic example. Remove credentials, personal records, device identifiers and deployment-specific values before sharing logs or screenshots.
Include a synthetic listing and expected score for scoring changes. Do not run automated tests that contact OLX or paid model endpoints.
Keep changes focused and update the affected documentation. Security-sensitive reports should follow SECURITY.md.