feat(04-03): the /cari command + venue card + OPEN deep link (D-01/D-02/D-10/D-11)

- cari.ts: /cari + /search twin, 128-char pre-search cap (T-04-21), honest
  no-match refusal (D-13), single sendVenue card with inline OPEN + alternates
- deep-link carrier corrected per RESEARCH Pitfall 1: web_app button URL
  carries ?dest=<plain slug id> — WebAppInfo has only a url field; no crypto
- origin from configured MINI_APP_URL only (never user input); blank origin
  omits the OPEN button instead of guessing (T-04-30)
- strings.ts: searchUsage/searchNoMatch/searchQueryTooLong/searchOpenButton/
  osmAttribution in en+ms; helpMenu gains the /cari line in both locales
- category-only logging law (D-15): four literal rejection categories, query
  never logged; success path logs nothing
- config: MINI_APP_URL in Env + wrangler vars (public, not secret) +
  .dev.vars.example + docs/CONFIGURATION.md row and sections
- bot deps gain @araharah/search (workspace link)
This commit is contained in:
araharah-executor
2026-10-24 01:13:13 +08:00
parent 3aec9542a3
commit a73a38830a
8 changed files with 181 additions and 5 deletions
+15 -2
View File
@@ -24,13 +24,14 @@ Configuration surfaces, by kind:
## Environment variables
Three variables reach the Worker (`bot/src/config.ts` declares them as a typed `Env` interface);
a fourth (`WEBHOOK_URL`) is consumed only by the one-time registration scripts, never by the Worker.
Four variables reach the Worker (`bot/src/config.ts` declares them as a typed `Env` interface);
a fifth (`WEBHOOK_URL`) is consumed only by the one-time registration scripts, never by the Worker.
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `BOT_TOKEN` | **Required** | none | Telegram Bot API token from @BotFather. Used by `createBot()` in `bot/src/bot/bot.ts` to construct the grammY `Bot`, and by `validateInitData()` in `bot/src/security/initdata.ts` as the HMAC key for Mini App `initData` verification. |
| `WEBHOOK_SECRET` | **Required** | none | Value Telegram echoes in the `X-Telegram-Bot-Api-Secret-Token` header on every update. `/telegram/webhook` constant-time-compares it **before** any bot processing or KV access. If missing or empty at request time the route fails closed with `500 Server misconfigured` — grammY's secret check would otherwise accept every request. |
| `MINI_APP_URL` | **Required for the deep link** | none | Public origin of the deployed Mini App (e.g. `https://araharah-bot.<account>.workers.dev`) — NOT a secret, so it lives in `bot/wrangler.jsonc`'s `vars`, not in a secret. The `/cari` venue card's OPEN button is built from it: `<MINI_APP_URL>/?dest=<gazetteer id>`. It must equal the Mini App URL registered in @BotFather (Bot API 10.2 enforces the origin). The Worker never derives it from user input — an unvalidated origin would be an open redirect. |
| `WEBHOOK_URL` | **Required for the one-time scripts** | none | Full origin + path of the deployed Worker, used by `bot/scripts/set-webhook.mjs` and `bot/scripts/set-menu-button.mjs`. If the value points at the workers.dev root (no path), `set-webhook.mjs` appends `/telegram/webhook` automatically. The Worker never reads this variable. |
| `chat_sessions` | **Required (binding)** | n/a | Not an env var but a Cloudflare KV namespace binding. Wired in `bot/wrangler.jsonc` under `kv_namespaces`; every `kv.put()` in `bot/src/kv/sessions.ts` and `bot/src/kv/location-pin.ts` writes through it with an explicit `expirationTtl`. |
@@ -42,6 +43,9 @@ BOT_TOKEN=
WEBHOOK_SECRET=
# Full origin + path; set-webhook.mjs auto-appends /telegram/webhook if only an origin is set.
WEBHOOK_URL=
# Public Mini App origin (not a secret) — lives in bot/wrangler.jsonc "vars" in production.
# The /cari venue card's OPEN button deep link is built from it: <MINI_APP_URL>/?dest=<id>.
MINI_APP_URL=
```
Script-level precedence: every script in `bot/scripts/` resolves a variable as
@@ -120,6 +124,9 @@ print a missing-variable list to stderr and `process.exit(1)` before any network
- `KV namespace id` — `bot/wrangler.jsonc` already carries a concrete id for `chat_sessions`; the
file's leading comment notes it was filled in after `npx wrangler kv namespace create chat_sessions`.
- `WEBHOOK_URL` path segment — auto-derived by `set-webhook.mjs` when the configured origin has no path.
- `MINI_APP_URL` — a non-secret configuration value declared in the Worker `Env` and shipped via
`bot/wrangler.jsonc`'s `vars` block (public origin, not a secret). When it is blank the `/cari`
OPEN button is omitted rather than guessed; the venue card and its alternates still send.
- KV TTLs — constants in source, not configuration: `SESSION_TTL_SECONDS = 900`
(`bot/src/kv/sessions.ts`) and `PIN_TTL_SECONDS = 300` (`bot/src/kv/location-pin.ts`). Both are
pinned by a grep gate in the test suite so no write path can persist without a TTL.
@@ -141,6 +148,9 @@ print a missing-variable list to stderr and `process.exit(1)` before any network
"not_found_handling": "none"
},
"run_worker_first": ["/api/*", "/telegram/*"],
// Public vars (NOT secrets): the deployed Mini App ORIGIN used to build
// the /cari venue card's OPEN deep link (<MINI_APP_URL>/?dest=<id>).
"vars": { "MINI_APP_URL": "https://araharah-bot.<account>.workers.dev" },
"kv_namespaces": [{ "binding": "chat_sessions", "id": "59f58a54bc44483e89872b20a005a5eb" }]
}
```
@@ -154,6 +164,9 @@ Key notes:
- `not_found_handling: "none"` — unmatched paths are not served the SPA `index.html` fallback.
- `workers_dev: true` serves on the `<name>.<account>.workers.dev` URL, which is the `WEBHOOK_URL`
target.
- `vars: { "MINI_APP_URL": ... }` — a **public** origin shipped as a plain var (never a secret):
the deployed Mini App origin the `/cari` venue card's OPEN button deep link is built from. It
must equal the Mini App URL registered in @BotFather; see the env-var table above.
<!-- VERIFY: the Cloudflare account subdomain backing `araharah-bot.<account>.workers.dev` is not recorded in the repository. -->