OLX Deal Finder - Track OLX.ro listings, detect price drops, and surface deals on a self-hosted mobile dashboard.
Translation pending
CommercePython · ShellGitHub @zandaulionOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Track OLX.ro listings, price drops and market-relative deals in a self-hosted mobile dashboard.
Translation pending
Taken from the repository README
Loading the full README.
Read the full 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.