A two-piece system that turns r/buildapcsales into push notifications you actually want to see.
┌──────────────────────────────┐ ┌─────────────────────────────┐
│ Synology NAS (or VPS) │ │ Android phone │
│ │ │ │
│ bapcs-alerts-backend │ │ bapcs-alerts client │
│ ┌────────────┐ │ │ ┌──────────────┐ │
│ │ poller │ every 30s │ │ │ Compose UI │ │
│ │ → reddit │────────────►│ │ │ Rules / Deals│ │
│ │ → parser │ │ │ │ / Settings │ │
│ │ → rules │ │ │ └──────────────┘ │
│ │ → fcm │─────────────┼────────►│ ▲ │
│ └────────────┘ │ push │ │ │
│ express REST ◄────────────┼─────────┼─ rule CRUD, register token │
│ sqlite (data/) │ HTTPS │ │
└──────────────────────────────┘ └─────────────────────────────┘
| Path | What it is |
|---|---|
backend/ |
Node 20 + TypeScript service (poller, REST API, FCM push) |
android/ |
Kotlin + Compose Android app |
docs/ |
Notes, design decisions |
Each subdirectory has its own README with concrete setup steps.
- Backend prerequisites. Reddit script app, Firebase service-account
JSON, a random
API_TOKEN. See backend/README.md. - Deploy backend.
docker compose up -d --buildon the NAS. - Configure Android client. Drop
google-services.jsonfrom the same Firebase project intoandroid/app/. Build, install, point Settings at the backend URL +API_TOKEN, register the device. - Create rules. Open the Rules tab, add e.g. "GPU under $500":
include
rtx, rx 7, retailersAmazon, Newegg, categoriesGPU, max price500.00, channel iddeals_gpu, importancehigh. - Wait. Next time a matching post hits r/buildapcsales
/new, you get a push.
- Per-rule filtering: include/exclude keywords, retailer, category/flair, max price, min upvotes, MIR exclusion.
- Per-rule Android notification channels & priority.
- EXPIRED-flair detection. Refreshes flair on every poll; deals killed by mods don't notify, and existing entries get re-rendered as strikethrough in the Deals tab.
- Repost dedup. Same canonical URL across reposts collapses into one
notification per
REPOST_DEDUP_HOURSwindow (affiliate/tracking params stripped before comparison). - Quiet hours. Per-rule
HH:MMstart/end window in the backend's configured TZ; pushes during the window are dropped (post still stored). - Test push button. Send a
[TEST]notification using the most recent matching post (or a synthetic one) without waiting for a real deal. - Backend GitHub Actions type-check on every push.
- Multi-user. One set of rules, one device pool per backend.
- Pull-to-refresh / pagination on the lists.
- Price-history tracking. Each post is a snapshot; we don't track edits or repost detection.
- Discord integration. Out of scope for now.
- Rate-limit pressure handling. At 30s × one subreddit you're far below Reddit's 60/min OAuth limit; if you add subs, add backoff.
src/reddit.ts— OAuth +/newfetchsrc/parser.ts— title heuristics (price, retailer, MIR, category)src/rules.ts— rule CRUD + matchersrc/store.ts— posts, devices, sent-logsrc/fcm.ts— FCM multicast w/ token cleanupsrc/poller.ts— the loop that ties it all togethersrc/api.ts— Express REST APIsrc/index.ts— bootstrap
data/Models.kt— DTOs (mirror backend types)data/Api.kt— Retrofit interface + factorydata/Settings.kt— DataStore-backed url + tokendata/ServiceLocator.kt— hand-rolled DInotify/PushService.kt— FCM service (token rotation)notify/Channels.kt— creates per-rule channels on saveui/AppRoot.kt— bottom-nav scaffoldui/PostsScreen.kt/RulesScreen.kt/RuleEditorScreen.kt/SettingsScreen.kt— the UI