Skip to content
Venius24Public

About

Self-hosted price monitor for Kaspi.kz, OLX.kz, Krisha.kz and flights: explainable Hot Score and Telegram deal alerts.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ’Έ CheapKZ

Self-hosted price monitoring for Kazakhstan β€” with "BUY NOW" alerts in Telegram.

Watches Kaspi.kz, OLX.kz, Krisha.kz and flight prices, scores every deal with an explainable 0–100 Hot Score, and pings you the moment a price becomes anomalously low.

CI License: MIT Node TypeScript Tests

πŸ‡¬πŸ‡§ English Β· πŸ‡·πŸ‡Ί Русский Β· Architecture Β· Decisions (ADR)


Why this exists

Price trackers either need a SaaS subscription or break the first time a shop adds a captcha. CheapKZ is a small, readable, single-process service you can run on a $5 VPS:

  • paste a link into the Telegram bot β†’ it starts watching,
  • it learns the price history of that item,
  • it alerts you only when the price is genuinely good and says why.

It is also a deliberate exercise in building a scraper-backed service responsibly: polite by default, hard to abuse, and heavily tested.

Highlights

πŸ”₯ Explainable Hot Score 0–100 from discount vs. old price, position in the item's own history, depth below its maximum and freshness. Alerts read like "βˆ’22% from old price Β· cheaper than 91% of its history". (ADR 0005)
πŸ›° Anomaly detector select Krisha districts on a live map or Kaspi categories β€” a periodic scan flags listings β‰₯ N% cheaper per mΒ² than similar flats nearby and items that suddenly drop below their own median price
🎯 Alert rules target price · drop of N% · historical-low percentile · new listings matching keyword / price filters
🧠 Noise control per-rule cooldowns, dedup, quiet hours that hot alerts can break through, mute, daily digest, price-trend regression ("wait or buy")
πŸ›‘ Anti-ban HTTP layer per-host token bucket + jitter + global concurrency, UA rotation, weighted proxy pool with exponential cooldown, per-domain circuit breaker, captcha/DDoS-Guard detection
πŸ”’ Security-minded SSRF guard re-validated on every redirect hop, secret redaction in logs/errors, constant-time token compare, HTML-escaped bot output
πŸ“ˆ Operable Prometheus /metrics, /health, retention job, Docker image running as non-root, SQLite in WAL mode
🧩 Extensible a source is one file returning Offer[]; a generic CSS-selector adapter covers any other site without code
πŸ§ͺ Tested 159 tests: pure-logic unit tests, parser tests against fixtures, HTTP layer against a real local server, API integration tests

How it works

 Telegram bot ─┐                                   β”Œβ”€β–Ά Kaspi / OLX / Krisha / Flights / generic
 REST + React ─┼─▢ SQLite ◀─ Scheduler ─▢ runWatcher ─▢ HttpGuard  (SSRF Β· limiter Β· proxies Β· breaker)
               β”‚      β–²                        β”‚
               β”‚      └────── alerts ◀── hotScore() + evaluateRules()   (pure functions)
               └─ Notifier ──▢ Telegram DM (quiet hours Β· mute Β· digest)

One watcher run: fetch β†’ normalise β†’ filter β†’ score β†’ evaluate rules β†’ cooldown β†’ persist β†’ deliver. Full walk-through in docs/ARCHITECTURE.md.

Quick start

Requires Node.js β‰₯ 23 β€” CheapKZ uses the built-in node:sqlite, so there is nothing native to compile.

git clone https://github.com/Venius24/cheapkz.git
cd cheapkz
npm install
cp .env.example .env     # set ADMIN_TOKEN (and TELEGRAM_BOT_TOKEN for the bot)
npm run dev              # http://localhost:8080

Docker

cp .env.example .env
mkdir -p data && sudo chown -R 1000:1000 data   # container runs as uid 1000
docker compose up -d --build

Production build

npm run build && npm start

In NODE_ENV=production start-up fails if ADMIN_TOKEN is empty, shorter than 16 chars, or still the example value.

Configuration

Variable Purpose
ADMIN_TOKEN protects the dashboard and API (openssl rand -hex 32)
TELEGRAM_BOT_TOKEN enables the bot (from @BotFather); without it the web UI still works
TRAVELPAYOUTS_TOKEN enables flight monitoring
PROXIES comma-separated http://user:pass@host:port list
MAX_CONCURRENCY, MIN_DELAY_MS global politeness knobs (default 3, 900..2400)
RETENTION_DAYS price-history retention (default 365, 0 = keep forever)
ALLOW_PRIVATE_TARGETS disables the SSRF guard β€” only for monitoring your own internal services

Everything is documented in .env.example.

Telegram bot

<paste a kaspi / olx / krisha link>   start watching instantly
/list                                 watchers with ⏸ β–Ά ↻ πŸ—‘ buttons
/set 12 199990                        target price for watcher 12
/drop 12 15                           alert on a drop of β‰₯ 15%
/flights ALA NQZ 2026-09-12           watch flights
/hot                                  best deals right now
/quiet 23:00 07:00                    quiet hours (πŸ”₯ alerts break through)
/mute 2h Β· /digest 9 Β· /stats

REST API

All routes except /health need Authorization: Bearer <ADMIN_TOKEN>.

# create a watcher β€” the kind is auto-detected from the URL
curl -H "Authorization: Bearer $T" -H "content-type: application/json" \
  -d '{"url":"https://kaspi.kz/p/apple-iphone-15/105621344/","target_price":189990}' \
  http://localhost:8080/api/watchers

curl -H "Authorization: Bearer $T" "http://localhost:8080/api/deals?level=hot"
curl -H "Authorization: Bearer $T" http://localhost:8080/metrics     # Prometheus

Any other website, no code needed:

{ "url": "https://example.com/product", "kind": "generic",
  "params": { "selector": ".price", "matchAll": false } }

Engineering decisions

The interesting part of this repo is why it is built the way it is. Each decision has a short record in docs/adr/:

Decision Short version
node:sqlite, not a native driver zero native deps, trivial backups; cost: Node β‰₯ 23
Single egress point adapters cannot skip rate limits, proxies or SSRF checks
Pure scoring & rules the product's core logic has no I/O, so it is trivially testable
Manual redirects a public URL redirecting to 169.254.169.254 must not slip through
Weighted score, not ML small per-item data; users must be able to trust and read the result

Project layout

src/
  api/        Fastify REST, SPA hosting, /metrics
  bot/        grammY Telegram bot
  core/       hotScore, rules, formatting, notifier   (pure logic lives here)
  db/         node:sqlite wrapper, migrations, queries
  http/       HttpGuard, RateLimiter, Breaker, ProxyPool, SSRF guard
  sources/    kaspi Β· olx Β· krisha Β· flights Β· generic
  worker/     runWatcher pipeline + Scheduler
web/          React + Tailwind dashboard (Vite)
tests/        vitest suites
fixtures/     recorded HTML/JSON for parser tests
docs/         architecture + ADRs

Development

npm run typecheck   # strict TS, server + web
npm test            # 159 tests
npm run build

See CONTRIBUTING.md for how to add a new source.

Responsible use

CheapKZ is built to be polite: low default rates, jitter, caching, circuit breakers. Keep polling intervals at 15–30 minutes or more, respect each site's Terms of Service, and don't use it for bulk scraping. The project is not affiliated with Kaspi, OLX, Krisha or Aviasales; all trademarks belong to their owners.

Roadmap

  • More delivery channels (email, Pushover)
  • Seasonality-aware price forecast
  • Currency-rate and subscription monitoring
  • Pin the resolved IP per request to remove the DNS-rebinding window (ADR 0004)

License

MIT Β© 2026 Venius24

About

Self-hosted price monitor for Kaspi.kz, OLX.kz, Krisha.kz and flights: explainable Hot Score and Telegram deal alerts.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages