|
| 1 | +## Quick orientation |
| 2 | + |
| 3 | +This repository implements a Telegram bot that watches iOS App Store releases and notifies subscribers. The codebase is split into three logical modules: |
| 4 | + |
| 5 | +- `ReleaseInformerBot` (executable): main bot logic, Telegram handlers, Vapor app scaffolding — see `Sources/ReleaseInformerBot/configure.swift` and `Sources/ReleaseInformerBot/entrypoint.swift`. |
| 6 | +- `ReleaseWatcher` (service): background watcher that iterates subscriptions and sends notifications — see `Sources/ReleaseWatcher/ReleaseWatcher.swift`. |
| 7 | +- `Shared` (library): models and persistence helpers (CouchDB) — see `Sources/Shared/Managers/DBManager.swift` and `Sources/Shared/Models/Subscription.swift`. |
| 8 | + |
| 9 | +Read these files first to understand the end-to-end flow. |
| 10 | + |
| 11 | +## Big-picture data flow |
| 12 | + |
| 13 | +1. User sends command to the Telegram bot (handled by `BotHandlers` in `Sources/ReleaseInformerBot/TGBot/BotHandlers.swift`). |
| 14 | +2. Handlers call `DBManager` (`Sources/Shared/Managers/DBManager.swift`) to create/list/delete `Subscription` documents in CouchDB. |
| 15 | +3. `ReleaseWatcher` periodically fetches all subscriptions (`getAllSubscriptions()`), queries the iTunes API via `SearchManager` (`Sources/Shared/Managers/SearchManager.swift`) and updates subscriptions with new versions. |
| 16 | +4. When a new version is detected, `ReleaseWatcher` uses the `TGBot` instance (set in `configure.swift` via `BotActor`) to send formatted HTML messages. |
| 17 | + |
| 18 | +Key integration points: `DBManager` <-> CouchDB, `SearchManager` <-> iTunes API (https://itunes.apple.com), `AsyncHttpTGClient` <-> Telegram API. |
| 19 | + |
| 20 | +## Concurrency & runtime patterns to keep in mind |
| 21 | + |
| 22 | +- Actors are used heavily for thread-safety: `DBManager`, `SearchManager`, `ReleaseWatcher`, `BotActor`. Prefer `async/await` and actor isolation when changing shared state. |
| 23 | +- `ReleaseWatcher` uses two `DispatchSourceTimer`s: a 5-minute `timer` for bulk runs and a 2-second `appCheckTimer` that processes a queue; it enforces small delays (2s) between individual chat notifications. |
| 24 | +- The `entrypoint` contains commented-out code for attempting to install NIO as the global executor — be cautious if enabling it as it can change shutdown behavior. |
| 25 | + |
| 26 | +## Persistence and DB conventions |
| 27 | + |
| 28 | +- Database name: `release_bot` (hard-coded in `DBManager`). |
| 29 | +- `DBManager.setupIfNeed()` will create the DB and add a design doc with two views: `by_bundle` and `by_chat`. The design doc id is `_design/list`. |
| 30 | +- `Subscription` maps CouchDB keys with coding keys (bundle ID stored as `bundle_id`) — see `Sources/Shared/Models/Subscription.swift`. |
| 31 | +- When adding versions, the project keeps the last 5 versions (see `DBManager.addNewVersion(...)`). |
| 32 | + |
| 33 | +If you change the CouchDB credentials/host you must update `fileprivate let couchDBClient = CouchDBClient(...)` inside `DBManager.swift`. |
| 34 | + |
| 35 | +## Bot & Telegram specifics |
| 36 | + |
| 37 | +- Bot token is read from environment variable `apiKey` in `configure.swift`. |
| 38 | +- The project uses `SwiftTelegramSdk` and a custom `AsyncHttpTGClient` (`Sources/ReleaseInformerBot/TGBot/AsyncHttpTGClient.swift`) which: |
| 39 | + - Defaults to multipart/form-data for most requests. |
| 40 | + - Limits response body reads to ~1MB for Telegram responses. |
| 41 | + - Throws a descriptive `BotError` when Telegram returns ok=false. |
| 42 | +- Messages use HTML parse mode; handlers build HTML strings (see `BotHandlers.makeSearchResultsMessage` and `makeListMessage`). Preserve HTML encoding when editing messages. |
| 43 | + |
| 44 | +## Search & iTunes quirks |
| 45 | + |
| 46 | +- `SearchManager` performs HTTP GETs against the iTunes Search/Lookup APIs and contains a `processJSONString(_:)` sanitiser that: |
| 47 | + - Escapes control characters inside JSON strings |
| 48 | + - Replaces non-breaking spaces |
| 49 | + This behaviour exists because the iTunes API sometimes returns characters that break `JSONDecoder`. If you modify parsing, keep this sanitiser or add robust tests. |
| 50 | + |
| 51 | +## How to run, build, and test |
| 52 | + |
| 53 | +- Build: `swift build` |
| 54 | +- Run locally: set the Telegram token and run the executable: |
| 55 | + |
| 56 | +```bash |
| 57 | +export apiKey="<YOUR_TELEGRAM_BOT_TOKEN>" |
| 58 | +swift run |
| 59 | +``` |
| 60 | + |
| 61 | +- Tests: `swift test` (tests use `Application.make(.testing)` from VaporTesting; see `Tests/ReleaseInformerBotTests/ReleaseInformerBotTests.swift`). |
| 62 | + |
| 63 | +Notes: CouchDB must be reachable for the app to initialize successfully; `DBManager.setupIfNeed()` will try to create DB and views on startup and will exit the process on failure (see `configure.swift`). |
| 64 | + |
| 65 | +## Common change patterns & examples |
| 66 | + |
| 67 | +- Adding a new bot command: implement a handler in `BotHandlers` and register it from `addHandlers(bot:)`. |
| 68 | +- To send a message from background code: obtain the `TGBot` via `BotActor` (see `configure.swift`) and call `bot.sendMessage(...)` from an async context. |
| 69 | +- To add a new view or query in CouchDB: add the JavaScript map to the design document in `DBManager.setupIfNeed()` and ensure any changes keep existing docs compatible. |
| 70 | + |
| 71 | +## Files to inspect when debugging |
| 72 | + |
| 73 | +- `Sources/ReleaseInformerBot/configure.swift` — initialization order (DB -> bot -> watchers -> routes) |
| 74 | +- `Sources/Shared/Managers/DBManager.swift` — DB access patterns and CouchDB client usage |
| 75 | +- `Sources/ReleaseWatcher/ReleaseWatcher.swift` — timers, run loop, and notification flow |
| 76 | +- `Sources/ReleaseInformerBot/TGBot/*` — bot client, handlers, and HTTP client |
| 77 | + |
| 78 | +## Quick tips for agents |
| 79 | + |
| 80 | +- Preserve actor isolation when editing shared state. |
| 81 | +- Respect the 2s sleeps used to rate-limit notifications in `ReleaseWatcher` and `BotHandlers` — removing them may trigger API rate limits. |
| 82 | +- When modifying message text, keep HTML tags and avoid unescaped user content. |
| 83 | +- Add unit tests for parsing changes in `SearchManager.processJSONString(_:)` when touching JSON handling. |
| 84 | + |
| 85 | +If anything here looks incomplete or you want more examples (e.g., an example test or a short diagram), tell me which area to expand. |
0 commit comments