diff --git a/README.md b/README.md index 67397ed..0f1b29d 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,9 @@ their own addresses in both forms. ## 3. 5-minute setup Requires a reachable Redis instance and a JMAP session plus a Telegram bot token. +If you do not have those yet, the hand-holding walkthrough in +[`docs/quickstart.md`](docs/quickstart.md) covers BotFather, chat-id lookup, app-password +creation and the full first-run from zero. **This is a local container smoke test.** Production deployment can use any platform that can run the backend container, inject encrypted secrets, and connect to externally managed @@ -158,6 +161,7 @@ mail. See §2. | Document | What it answers | |---|---| +| [`docs/quickstart.md`](docs/quickstart.md) | Hand-holding first run: create a Telegram bot, find the chat id, deploy one backend, fill the form, register callbacks | | [`docs/design.md`](docs/design.md) | Why the system is shaped this way: data flow, JMAP semantics, Redis streams, AI consent rules | | [`docs/deployment.md`](docs/deployment.md) | How to deploy: Dockerfile, secrets, Redis hosting, webhook/push/reconcile routing, multi-instance load balancing | | [`docs/reference.md`](docs/reference.md) | **Single source of truth for verifiable facts** — Redis keys and TTLs, error codes, routes, environment layers, budgets | diff --git a/README.zh-CN.md b/README.zh-CN.md index 7037aec..8105315 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -55,6 +55,8 @@ MessageWeave 位于 JMAP 邮件服务器(例如 Stalwart)与 Telegram bot ## 3. 五分钟跑起来 需要可用的 Redis 服务、一个 JMAP session 与一个 Telegram bot token。 +如果这些你还没有,[`docs/quickstart.zh-CN.md`](docs/quickstart.zh-CN.md) 有手把手教程:BotFather +申请、chat id 获取、应用密码创建与首次配置全过程。 这段是**本地容器冒烟测试**。生产部署可以用任何能跑起后端容器、注入加密 secret 并连到外部托管 Redis 的平台,见 `docs/deployment.md` §3 与 `docs/reference.md` 的平台相关部分。 @@ -118,6 +120,7 @@ Telegram 发送。它由双因子开关控制——进程必须带 `--debug` ** | 文档 | 回答什么 | |---|---| +| [`docs/quickstart.zh-CN.md`](docs/quickstart.zh-CN.md) | 手把手首次运行:创建 Telegram bot、找到 chat id、部署一个后端、填表、注册回调 | | [`docs/design.zh-CN.md`](docs/design.zh-CN.md) | 为什么这样设计:数据流、JMAP 语义、Redis streams、AI 授权规则 | | [`docs/deployment.zh-CN.md`](docs/deployment.zh-CN.md) | 怎么部署:Dockerfile、secrets、Redis 托管、webhook/push/对账路由、多实例负载均衡 | | [`docs/reference.zh-CN.md`](docs/reference.zh-CN.md) | **可核对事实的唯一权威来源**——Redis 键与 TTL、错误码、路由、环境变量分层、预算常量 | diff --git a/docs/charter.md b/docs/charter.md index 13ff2b3..b70a86a 100644 --- a/docs/charter.md +++ b/docs/charter.md @@ -171,6 +171,7 @@ Answer only one question per document; **tables, lists and values must not be du |---|---|---| | [`../AGENTS.md`](../AGENTS.md) | Universal, language-independent code writing and environment building specifications | Quality rules | | `README.md` / `README.zh-CN.md` | User-oriented: what is it, how to run it, how to configure it | User-visible facts (information must be equivalent in Chinese and English) | +| `docs/quickstart.md` / `docs/quickstart.zh-CN.md` | Hand-holding first run: create a Telegram bot, find the chat id, deploy one backend, fill the form, register callbacks | Steps and decisions (field semantics, defaults and TTLs stay in `docs/reference.md`) | | `docs/design.md` | Why it is designed this way: data flow, module boundaries, state machines, error handling | Architectural intent | | `docs/deployment.md` | How to deploy: Dockerfile, secrets, gateway, multiple instances, cron | Deployment and operation and maintenance | | `docs/reference.md` | The single authoritative source of verifiable facts: routing, Redis keys and TTLs, configuration items, error codes, outbound constants | **verifiable facts** | diff --git a/docs/charter.zh-CN.md b/docs/charter.zh-CN.md index fd1919a..7a4a017 100644 --- a/docs/charter.zh-CN.md +++ b/docs/charter.zh-CN.md @@ -171,6 +171,7 @@ Stalwart 与 Telegram 的业务凭据已在真机联调通过——出站、查 |---|---|---| | [`../AGENTS.md`](../AGENTS.md) | 通用、语言无关的代码编写与环境构建规范 | 质量规则 | | `README.md` / `README.zh-CN.md` | 面向使用者:是什么、怎么跑、怎么配置 | 用户可见事实(中英必须信息对等) | +| `docs/quickstart.md` / `docs/quickstart.zh-CN.md` | 手把手首次运行:创建 Telegram bot、找到 chat id、部署一个后端、填表、注册回调 | 步骤与决策(字段语义、默认值、TTL 归 `docs/reference.md`) | | `docs/design.md` | 为什么这样设计:数据流、模块边界、状态机、错误处理 | 架构意图 | | `docs/deployment.md` | 怎么部署:Dockerfile、secrets、网关、多实例、cron | 部署与运维 | | `docs/reference.md` | 可核对事实的唯一权威来源:路由、Redis 键与 TTL、配置项、错误码、出站常量 | **可核对事实** | diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..6484074 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,203 @@ +# 1. Quickstart + +Chinese: [quickstart.zh-CN.md](quickstart.zh-CN.md) + +This page is a from-zero walkthrough for a human operator: create a Telegram bot, deploy +one backend, fill the configuration page, register the callbacks, and get the first +notification. It only contains steps and decisions. Field semantics, defaults, TTLs and +error codes live in [reference.md](reference.md); production, scaling and gateway +operations live in [deployment.md](deployment.md). + +## 1.1 What you need + +- A mailbox on a JMAP server you administer (this guide assumes Stalwart), HTTPS only. +- A Telegram account. +- A Redis endpoint with persistence (Upstash's free tier works; it is TLS-only, so the URL + starts with `rediss://`). +- A machine or platform that can run one container and expose one HTTP port. +- One public HTTPS origin that can receive webhooks — your own domain, a tunnel, or a + Cloudflare route. Telegram and Stalwart both require HTTPS. +- Optional: an OpenAI-compatible LLM key, a Cloudflare account if you want the gateway. + +Nothing else is required: no Postgres, no message broker, no Kubernetes. + +## 1.2 Create the Telegram bot + +1. In Telegram open a chat with `@BotFather` and send `/newbot`. +2. Pick a display name, then a username that ends in `bot` or `_bot`. +3. BotFather replies with a token that looks like `1234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw1`. + That string is the bot token. Treat it like a password: anyone holding it can read your + webhook traffic and send messages as your bot. It is never echoed back by the API once saved. +4. Send the new bot any message (for example `/start`) from the account or group that should + receive notifications. Until you do, Telegram has no chat to deliver to. + +## 1.3 Find the chat id + +1. Open `https://api.telegram.org/bot/getUpdates` in a browser, replacing `` with + the token from step 3. +2. In the JSON, find `result[].message.chat.id`. Private chats give a positive number such as + `123456789`; groups and supergroups give a negative one such as `-1001234567890`. +3. For a group, add the bot to the group first, then send a message in the group before you call + `getUpdates`. The bot only sees messages in groups after you disable privacy mode under + `@BotFather` → `/setprivacy` → `Disable`, otherwise group messages never reach it. + +You will use this id twice in step 7: as the notification target and as a member of the inbound +allowlist. The allowlist must contain at least one id — an empty allowlist is rejected as invalid +configuration rather than treated as "allow everyone". + +## 1.4 Get the JMAP credentials + +1. In your Stalwart admin, create an application password for the mailbox — an app password, not + the account login password. The backend authenticates with HTTP Basic over JMAP. +2. The session URL is your mail origin: `https://mail.example.com`. You may also paste + `https://mail.example.com/.well-known/jmap`; the backend collapses both to the origin, and + rejects any URL with embedded credentials, a query string or a fragment. +3. The JMAP account id is optional. Leave it empty: after the first successful save the backend + reads it back from the server and the page shows it. + +## 1.5 Run the backend + +The container needs exactly two environment variables: + +```bash +# .env — never commit this file +REDIS_URL=rediss://default:@:6379 +CONFIG_ENCRYPTION_KEY=<64 hex characters, from: openssl rand -hex 32> +``` + +- `REDIS_URL` is your only state store. Back it up like a database. +- `CONFIG_ENCRYPTION_KEY` doubles as the SPA admin credential and the encryption key for stored + business secrets. If you lose it you can no longer read the configuration page or decrypt the + stored secrets; if you rotate it, previously stored secrets become unreadable. + +Then build and start: + +```bash +docker build -t messageweave . +docker run --rm --env-file .env -p 8080:8080 messageweave +curl localhost:8080/healthz # → ok +``` + +`GET /healthz` is a static liveness reply and never touches Redis. The readiness report is +`GET /ready`: it answers 503 until the configuration exists and every configured upstream is +reachable, and 200 with a JSON component report once they are. Point your HTTPS reverse proxy or +tunnel at this port now — steps 8 and 9 need a public HTTPS origin. + +## 1.6 Sign in to the configuration page + +1. Open the page in a browser. If the backend started with the required variables the first card + is **Create admin session**. +2. Paste `CONFIG_ENCRYPTION_KEY` — not the Redis password. This mints an admin session that lives + only in the page's memory for 30 minutes; reloading the page logs you out. + +## 1.7 Fill in the business configuration + +The form is a full replacement, not a patch: whatever you submit becomes the whole stored +configuration. The sections: + +- **01 JMAP mailbox** — session URL and username, plus the app password from step 4. +- **02 Telegram and access auth** — bot token, notification chat id, allowlist, and three secrets + you make up yourself: webhook secret, reconcile token and worker token. Generate them with + `openssl rand -hex 32` each. The webhook secret must match on the Telegram side later; the + reconcile and worker tokens protect the scheduler endpoints. +- **03 LLM service** — optional. Leave disabled unless you want AI digests; if you enable it, the + HTTPS base URL, model name and API key all become required. +- **04 Notification display** — the time zone used to render received times in notifications. + +Then, in order: + +1. Click **Test connection**. It preflights the credentials without saving anything. +2. Click **Save business config and hot-reload**. The first save must contain every required + field or the server answers 422 and stores nothing. +3. Read the result line under the button: `runtime_applied: true` means the new runtime was + installed on this backend. `false` means it saved but the runtime was not installed, and the + `warnings` array says which component failed — most often an unreachable `jmap_session_url`. + Fix the field and save again; `/ready` stays 503 until it goes green. +4. If the save answers **409 conflict**, someone else (another tab, a second operator) saved in + between. Reload the page to pick up their values, then re-apply your edit. + +Secrets are write-only: the page never shows a stored secret again, and submitting an empty secret +field keeps the previously stored value — clear a secret deliberately in a separate edit. + +## 1.8 Register the callbacks + +Still on the same page, under **External callbacks**: + +1. Paste the public HTTPS origin that reaches this backend, scheme and host only, no path — + for example `https://mw.example.com`. If you put the Cloudflare gateway in front, use the + gateway's origin, not the backend's: callbacks must survive a backend restart, and the + gateway is what stays up. +2. Click **Register callbacks**. One click registers both directions: the Telegram webhook at + `/webhook/tg` and the Stalwart push subscription at `/push/jmap`. Verification is handled + automatically; the page reports the result. + +Registration is explicit and repeatable. Registering a new origin cancels the previous subscription, +so the old origin stops receiving pushes. A failed registration never removes the old one — if it +can't reach Telegram, the previous webhook stays in place. If it did reach Telegram but not +Stalwart, the page reports a partial failure and tells you to click again. + +Verify from a terminal: + +```bash +curl -s "https://api.telegram.org/bot/getWebhookInfo" +# expect "url": "https://mw.example.com/webhook/tg" and "pending_update_count": 0 +``` + +## 1.9 Turn on processing + +Flip **Enable business processing** at the top of the page. This is the master switch: with it off, +webhooks are accepted but `/worker` and `/reconcile` refuse to drain, so nothing reaches Telegram. +With it on, incoming mail and messages are enqueued and the worker drains them. + +## 1.10 Verify end to end + +1. Send yourself an email. Within a few seconds the bot should post a notification with the + sender, subject and received time — never the body. That path alone proves JMAP ingest, + Redis, the queue and the Telegram send all work. +2. In Telegram try `/search 发票`. Note: the search triggers are the Chinese words `搜索`, `查找` + and `检索`, plus the `/search` command — English keywords like `search invoice` are not + recognised as triggers. +3. Try `/summary ` from the notification's email id. The first call asks for + confirmation; the summary only goes out after you confirm, and only if the LLM section is + enabled. + +## 1.11 Schedule the drain + +Push delivery can be delayed or missed; an external scheduler is the safety net. On any machine +that can reach your origin, every 5–10 minutes: + +```bash +MW_APP_URL=https://mw.example.com \ +MW_WORKER_TOKEN= \ +MW_RECONCILE_TOKEN= \ +scripts/cron-drain.sh +``` + +The script drains the queue (`/worker`) and rescans for missed pushes (`/reconcile`); both return +204 with an empty body on success. `MW_RECONCILE_TOKEN` is optional — if omitted, the reconcile +step is skipped and only the queue is drained. If you put the gateway in front, `/worker` must +reach the backend origin directly — see deployment.md for why. + +## 1.12 Optional: the Cloudflare gateway + +Skip this unless you want a load balancer across two backends. The Worker is optional, gives +free-tier Cloudflare users a load balancer, and is not a security layer: the backend origins stay +directly reachable either way. Setup lives in `cloudflare-worker/README.md`; when you use it, +register the callback origin from step 8 against the gateway. + +## 1.13 When something is wrong + +- **Nothing arrives after saving.** Check the enable toggle (step 9), then + `getWebhookInfo` (step 8). A `last_error_message` about a 401 means the webhook secret does not + match what Telegram sends — re-save section 02 and re-register. +- **`/ready` answers 503.** That body is just an error code — it does not name the failing + component. When `/ready` returns 200, its JSON has per-field booleans (`jmap`, `telegram`, + `redis`). Common causes are an unreachable `jmap_session_url`, a wrong app password, or + `runtime_applied: false` from the last save. +- **The form says conflict.** Two sessions saved at once; reload and redo (step 7.3). +- **The bot ignores you in a group.** Privacy mode is on; disable it under `@BotFather` (step 3). +- **`/search` answers with a help hint.** Your query started with a Latin word; the prefix triggers + are `搜索` / `查找` / `检索`. + +Field-level errors, TTLs, Redis keys and route tables: [reference.md](reference.md). +Scaling, secrets rotation, gateways and cron on the wire: [deployment.md](deployment.md). diff --git a/docs/quickstart.zh-CN.md b/docs/quickstart.zh-CN.md new file mode 100644 index 0000000..6104b70 --- /dev/null +++ b/docs/quickstart.zh-CN.md @@ -0,0 +1,177 @@ +# 1. 快速上手 + +英文:[quickstart.md](quickstart.md) + +本文是为人类运维者准备的从零开始指南:创建 Telegram bot、部署一个后端、填写配置页、注册回调、 +收到第一条通知。只包含步骤与决策。字段语义、默认值、TTL 与错误码在 [reference.md](reference.md); +生产、扩展、网关运维在 [deployment.md](deployment.md)。 + +## 1.1 你需要准备什么 + +- 一个你管理的 JMAP 邮箱(本指南以 Stalwart 为例),必须 HTTPS。 +- 一个 Telegram 账号。 +- 一个带持久化的 Redis 实例(Upstash 免费版可用;它是纯 TLS 的,URL 以 `rediss://` 开头)。 +- 一台能跑容器并暴露一个 HTTP 端口的机器或平台。 +- 一个能接收 webhook 的公网 HTTPS 域名——自有域名、隧道或 Cloudflare 路由都行。Telegram 与 + Stalwart 都要求 HTTPS。 +- 可选:OpenAI 兼容的 LLM 密钥;想要网关的话再来一个 Cloudflare 账号。 + +不需要别的:没有 Postgres、没有消息队列、没有 Kubernetes。 + +## 1.2 创建 Telegram bot + +1. 在 Telegram 中打开 `@BotFather` 的对话,发送 `/newbot`。 +2. 选一个显示名,再选一个以 `bot` 或 `_bot` 结尾的用户名。 +3. BotFather 会回复一串 token,形如 `1234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw1`。 + 这就是 bot token。当成密码对待:拿到它的人能读取你的 webhook 流量、以你的 bot 身份发消息。 + 一旦保存,API 不再回显它。 +4. 用应当接收通知的账号或群给新 bot 发任意一条消息(例如 `/start`)。在此之前 Telegram 没有可投递 + 的对话。 + +## 1.3 找到 chat id + +1. 在浏览器打开 `https://api.telegram.org/bot/getUpdates`,把 `` 替换为第 3 步拿到的 + token。 +2. 在 JSON 中找 `result[].message.chat.id`。私聊是正数,例如 `123456789`;群与超级群是负数,例如 + `-1001234567890`。 +3. 如果是群:先把 bot 拉进群,再在群里发一条消息,然后才调 `getUpdates`。bot 在群里只有在关闭隐私模式 + 后才能看到非命令消息:`@BotFather` → `/setprivacy` → `Disable`。 + +这个 id 在第 7 步要用两次:一次当通知目标,一次放进入站白名单。白名单必须至少一个 id——空名单会被 +判为非法配置而拒绝,不会当成「允许所有人」。 + +## 1.4 拿到 JMAP 凭据 + +1. 在 Stalwart 管理端为邮箱创建一个应用密码——是应用密码,不是账号登录密码。后端用 HTTP Basic 走 + JMAP。 +2. session URL 就是你的邮箱源站:`https://mail.example.com`。也可以粘贴 + `https://mail.example.com/.well-known/jmap`;后端会把两者都折叠成源站,并拒绝带凭据、查询串或片段 + 的 URL。 +3. JMAP account id 是可选的。留空:首次保存成功后后端会从服务器读回,页面会显示出来。 + +## 1.5 跑起后端 + +容器只需要两个环境变量: + +```bash +# .env — 不要提交这个文件 +REDIS_URL=rediss://default:@:6379 +CONFIG_ENCRYPTION_KEY=<64 位十六进制,用:openssl rand -hex 32> +``` + +- `REDIS_URL` 是你唯一的状态存储。像数据库一样备份它。 +- `CONFIG_ENCRYPTION_KEY` 兼作 SPA 管理员凭据与已存业务密钥的加密密钥。丢了它,你既登不上配置页, + 也解不开已存密钥;轮换它,之前存的密钥全部不可读。 + +然后构建并启动: + +```bash +docker build -t messageweave . +docker run --rm --env-file .env -p 8080:8080 messageweave +curl localhost:8080/healthz # → ok +``` + +`GET /healthz` 是静态存活回复,不碰 Redis。就绪报告是 `GET /ready`:在配置存在且所有已配置上游可达 +之前回答 503,之后回答 200 并带一份 JSON 组件报告。现在把你的 HTTPS 反代或隧道指向这个端口——第 8、 +9 步需要一个公网 HTTPS 域名。 + +## 1.6 登录配置页 + +1. 在浏览器打开页面。如果后端带着所需变量启动,第一张卡是 **Create admin session**。 +2. 粘贴 `CONFIG_ENCRYPTION_KEY`——不是 Redis 密码。这会签发一个管理员会话,只活在页面内存里,有效期 + 30 分钟;刷新页面即登出。 + +## 1.7 填写业务配置 + +表单是整存整换,不是增量补丁:你提交的就是整份存储配置。各节: + +- **01 JMAP mailbox**——session URL、用户名,以及第 4 步的应用密码。 +- **02 Telegram and access auth**——bot token、通知 chat id、白名单,加上你自己生成的三个密钥:webhook + 密钥、reconcile token 与 worker token。各自用 `openssl rand -hex 32` 生成。webhook 密钥之后要在 + Telegram 侧对得上;reconcile 与 worker token 保护调度端点。 +- **03 LLM service**——可选。不需要 AI 摘要就别开;一旦开启,HTTPS base URL、模型名与 API key 三者都 + 变成必填。 +- **04 Notification display**——通知里收信时间的渲染时区。 + +然后按顺序: + +1. 点 **Test connection**。它预检凭据但不保存任何东西。 +2. 点 **Save business config and hot-reload**。首次保存必须包含所有必填字段,否则服务器回答 422 且 + 什么也不存。 +3. 读按钮下方那行结果:`runtime_applied: true` 表示新运行时已在本后端装配;`false` 表示存了但运行时未 + 装配,`warnings` 数组会指出失败的组件——最常见是 `jmap_session_url` 不可达。改字段再存一次;`/ready` + 会一直 503 直到它变绿。 +4. 如果保存回答 **409 conflict**,说明有人(另一个标签页、第二个运维者)在中间也存了。刷新页面拿到 + 他们的值,再把你的改动重新套上去。 + +密钥是只写的:页面永不再显示已存密钥,提交空的密钥字段会保留之前的值——要清密钥就在单独一次编辑里 +故意留空。 + +## 1.8 注册回调 + +仍在同一页,**External callbacks** 区: + +1. 粘贴能到达本后端的公网 HTTPS 源站,只写 scheme + host,不带路径——例如 `https://mw.example.com`。 + 如果你把 Cloudflare 网关放在前面,就填网关的源站,不是后端的:回调要能在后端重启后仍然可达,而 + 网关才是那个不掉线的。 +2. 点 **Register callbacks**。一次点击注册双向:Telegram webhook 注册到 `/webhook/tg`,Stalwart push + 订阅注册到 `/push/jmap`。验证自动完成,页面上报结果。 + +注册是显式的、可重复的。注册新源站会取消上一个订阅,于是旧源站不再收到 push。失败的注册绝不会删掉 +旧订阅——如果连不上 Telegram,上一个 webhook 原样留着;如果连上了 Telegram 但没连上 Stalwart,页面会 +报部分失败并让你再点一次。 + +在终端验证: + +```bash +curl -s "https://api.telegram.org/bot/getWebhookInfo" +# 期望 "url": "https://mw.example.com/webhook/tg" 且 "pending_update_count": 0 +``` + +## 1.9 打开处理开关 + +把页面顶部的 **Enable business processing** 拨开。这是总开关:关着时 webhook 照收,但 `/worker` 与 +`/reconcile` 拒绝排空,所以什么也到不了 Telegram。打开后,入站邮件与消息才会进队列、由 worker 排空。 + +## 1.10 端到端验证 + +1. 给自己发一封邮件。几秒内 bot 应当贴出一条通知,含发件人、主题与收信时间——绝不包含正文。这一条 + 路径同时证明 JMAP 入站、Redis、队列与 Telegram 发送全通。 +2. 在 Telegram 里试 `/search 发票`。注意:搜索触发词是中文 `搜索`、`查找`、`检索`,外加 `/search` + 命令——`search invoice` 这类英文关键词不会被识别为触发。 +3. 从通知里的 email id 试 `/summary `。首次调用会请求确认;只有你确认之后、且 LLM 节开启时, + 摘要才会发出。 + +## 1.11 排程排空 + +Push 投递可能延迟或丢失;外部调度器是兜底。在任意一台能访问你源站的机器上,每 5–10 分钟跑一次: + +```bash +MW_APP_URL=https://mw.example.com \ +MW_WORKER_TOKEN= \ +MW_RECONCILE_TOKEN= \ +scripts/cron-drain.sh +``` + +脚本会排空队列(`/worker`)并重扫漏掉的 push(`/reconcile`);两者成功都回答 204 空 body。如果你把网关放 +在前面,`/worker` 必须直达后端源站——原因见 deployment.md。 + +## 1.12 可选:Cloudflare 网关 + +除非你想跨两个后端做负载均衡,否则跳过。Worker 是可选的,给免费层 Cloudflare 用户提供一组负载均衡器, +不是安全层:无论用不用它,后端源站都保持可直接访问。安装见 `cloudflare-worker/README.md`;用时把第 8 步 +的回调源站对准网关。 + +## 1.13 出问题时 + +- **保存后什么也没到。** 查处理开关(第 9 步),再查 `getWebhookInfo`(第 8 步)。`last_error_message` + 里提到 401,说明 webhook 密钥与 Telegram 发来的不一致——重存 02 节并重新注册。 +- **`/ready` 回 503。** 503 的 body 只是一个错误码,不会指名是哪个组件挂了。`/ready` 回 200 时,JSON + 里有逐组件布尔值(`jmap`、`telegram`、`redis`)。常见原因:`jmap_session_url` 不可达、应用密码错、或 + 上次保存 `runtime_applied: false`。 +- **表单提示冲突。** 两个会话同时存了;刷新重做(第 7.3 步)。 +- **bot 在群里不理你。** 隐私模式开着;去 `@BotFather` 关掉(第 3 步)。 +- **`/search` 回的是帮助提示。** 你的查询以拉丁词开头;触发词是 `搜索` / `查找` / `检索`。 + +字段级错误、TTL、Redis 键与路由表:[reference.md](reference.md)。 +扩展、密钥轮换、网关与 cron 在线上的细节:[deployment.md](deployment.md)。 diff --git a/scripts/docs_check/audit_anchor_semantics.py b/scripts/docs_check/audit_anchor_semantics.py index 4e9c4d3..eb89d24 100755 --- a/scripts/docs_check/audit_anchor_semantics.py +++ b/scripts/docs_check/audit_anchor_semantics.py @@ -49,7 +49,9 @@ DOCS = ['AGENTS.md', 'README.md', 'README.zh-CN.md', 'docs/design.md', 'docs/design.zh-CN.md', 'docs/deployment.md', 'docs/deployment.zh-CN.md', 'docs/reference.md', 'docs/reference.zh-CN.md', 'docs/retired.md', 'docs/retired.zh-CN.md', 'docs/opengaps.md', 'docs/opengaps.zh-CN.md', - 'docs/charter.md', 'docs/charter.zh-CN.md', 'cloudflare-worker/README.md', + 'docs/charter.md', 'docs/charter.zh-CN.md', + 'docs/quickstart.md', 'docs/quickstart.zh-CN.md', + 'cloudflare-worker/README.md', 'cloudflare-worker/README.zh-CN.md'] FILE = r'((?:[A-Za-z0-9_.-]+/)?[A-Za-z0-9_-]+)' diff --git a/scripts/docs_check/audit_anchors.py b/scripts/docs_check/audit_anchors.py index 9bb566c..b828181 100755 --- a/scripts/docs_check/audit_anchors.py +++ b/scripts/docs_check/audit_anchors.py @@ -45,7 +45,9 @@ DOCS = ['AGENTS.md', 'README.md', 'README.zh-CN.md', 'docs/design.md', 'docs/design.zh-CN.md', 'docs/deployment.md', 'docs/deployment.zh-CN.md', 'docs/reference.md', 'docs/reference.zh-CN.md', 'docs/retired.md', 'docs/retired.zh-CN.md', 'docs/opengaps.md', 'docs/opengaps.zh-CN.md', - 'docs/charter.md', 'docs/charter.zh-CN.md', 'cloudflare-worker/README.md', + 'docs/charter.md', 'docs/charter.zh-CN.md', + 'docs/quickstart.md', 'docs/quickstart.zh-CN.md', + 'cloudflare-worker/README.md', 'cloudflare-worker/README.zh-CN.md'] # `Ident` or `Module::Ident` inside backticks, optional () after. diff --git a/scripts/docs_check/check_sec_refs.py b/scripts/docs_check/check_sec_refs.py index 1e8b4d1..6fc7dc9 100755 --- a/scripts/docs_check/check_sec_refs.py +++ b/scripts/docs_check/check_sec_refs.py @@ -17,7 +17,9 @@ DOCS = ["AGENTS.md", "README.md", "README.zh-CN.md", "docs/design.md", "docs/design.zh-CN.md", "docs/deployment.md", "docs/deployment.zh-CN.md", "docs/reference.md", "docs/reference.zh-CN.md", "docs/retired.md", "docs/retired.zh-CN.md", "docs/opengaps.md", "docs/opengaps.zh-CN.md", - "docs/charter.md", "docs/charter.zh-CN.md", "cloudflare-worker/README.md", + "docs/charter.md", "docs/charter.zh-CN.md", + "docs/quickstart.md", "docs/quickstart.zh-CN.md", + "cloudflare-worker/README.md", "cloudflare-worker/README.zh-CN.md"] HEADING = re.compile(r"^(#{2,4})\s+(\d+(?:\.\d+)?\.?)\s") diff --git a/scripts/docs_check/check_tables.py b/scripts/docs_check/check_tables.py index d4a1fb3..1f44e39 100755 --- a/scripts/docs_check/check_tables.py +++ b/scripts/docs_check/check_tables.py @@ -6,7 +6,9 @@ DOCS = ['AGENTS.md', 'README.md', 'README.zh-CN.md', 'docs/design.md', 'docs/design.zh-CN.md', 'docs/deployment.md', 'docs/deployment.zh-CN.md', 'docs/reference.md', 'docs/reference.zh-CN.md', 'docs/retired.md', 'docs/retired.zh-CN.md', 'docs/opengaps.md', 'docs/opengaps.zh-CN.md', - 'docs/charter.md', 'docs/charter.zh-CN.md', 'cloudflare-worker/README.md', + 'docs/charter.md', 'docs/charter.zh-CN.md', + 'docs/quickstart.md', 'docs/quickstart.zh-CN.md', + 'cloudflare-worker/README.md', 'cloudflare-worker/README.zh-CN.md'] CODE = re.compile(r'`[^`\n]*`') diff --git a/scripts/docs_check/validate_docs.py b/scripts/docs_check/validate_docs.py index 5f87ed8..d0a6fe3 100755 --- a/scripts/docs_check/validate_docs.py +++ b/scripts/docs_check/validate_docs.py @@ -5,7 +5,9 @@ DOCS = ['AGENTS.md', 'README.md', 'README.zh-CN.md', 'docs/design.md', 'docs/design.zh-CN.md', 'docs/deployment.md', 'docs/deployment.zh-CN.md', 'docs/reference.md', 'docs/reference.zh-CN.md', 'docs/retired.md', 'docs/retired.zh-CN.md', 'docs/opengaps.md', 'docs/opengaps.zh-CN.md', - 'docs/charter.md', 'docs/charter.zh-CN.md', 'cloudflare-worker/README.md', + 'docs/charter.md', 'docs/charter.zh-CN.md', + 'docs/quickstart.md', 'docs/quickstart.zh-CN.md', + 'cloudflare-worker/README.md', 'cloudflare-worker/README.zh-CN.md'] errors = []