mirror of
https://github.com/vercel/chat.git
synced 2026-09-14 18:32:29 +08:00
4ac0455134
## summary adds `onMessageUpdated` and `onMessageDeleted`, so a bot can react when a message is edited or removed. Slack dispatches both today; other adapters can opt in later supersedes #549, which was verified there against real Slack webhooks. reopened from a branch in this repo with the original commits preserved and signed ```typescript bot.onMessageUpdated(async (thread, message, previousMessage) => { await mirror.update(message.id, message.text); }); bot.onMessageDeleted(async (event) => { await mirror.remove(event.messageId); }); ``` both are lifecycle events: they never route through `onNewMessage`, `onNewMention`, or `onSubscribedMessage`, and the concurrency strategies do not apply ### notes - **the bot's own edits are filtered.** slack sends a `message_changed` for every `chat.update`, and post-and-edit streaming calls it once per delta, so without this a single streamed reply would call the handler back repeatedly on its own message - **`previousMessage` is forwarded on edits.** slack sends the pre-edit message and it was being dropped. an edit handler usually needs the before to know what changed, so it is the optional third argument - **the two shapes differ deliberately.** an edit carries a full replacement message, so it gets `(thread, message, previousMessage?)`. a delete has no message, only the id of what was removed, so it gets an event. use `chat.thread(event.threadId)` when a delete handler needs one - **one thread id helper** now serves message, edit, and delete, so an edit cannot resolve to a different thread than the message it edits ## test plan core: - an edit dispatches to `onMessageUpdated` and not to the normal message handlers - the handler receives the pre-edit message as its third argument - the bot's own edits are skipped - a delete dispatches with normalized event data - both run inside the active conversation, so read tools built in these handlers stay scoped slack: - `message_changed` dispatches as an update, `message_deleted` as a delete - `previous_message` is forwarded, and left undefined when slack omits it - hidden unfurl updates stay ignored, hidden real edits still dispatch - message, edit, and delete resolve to one thread id in a flat DM and in a threaded `agent_view` DM verified against a real slack workspace over socket mode: editing and deleting a DM both routed to the same thread id as the original message --------- Co-authored-by: Miłosz Lenczewski <m.lenczewski@tidio.net>
183 lines
5.2 KiB
Plaintext
183 lines
5.2 KiB
Plaintext
---
|
||
title: Zalo
|
||
description: Community Zalo Bot adapter for Chat SDK using the Zalo Bot Platform API.
|
||
packageName: chat-adapter-zalo
|
||
slug: zalo
|
||
type: platform
|
||
tagline: Community Zalo Bot adapter for Chat SDK using the Zalo Bot Platform API. Buffered streaming, auto-chunking, typing indicators, and webhook signature verification.
|
||
community: true
|
||
mdxBody: true
|
||
features:
|
||
postMessage: yes
|
||
editMessage: no
|
||
deleteMessage: no
|
||
fileUploads:
|
||
status: partial
|
||
label: Inbound only
|
||
streaming:
|
||
status: partial
|
||
label: Buffered
|
||
scheduledMessages: no
|
||
cardFormat: no
|
||
buttons: no
|
||
linkButtons: no
|
||
selectMenus: no
|
||
tables: no
|
||
fields: no
|
||
imagesInCards: no
|
||
modals: no
|
||
slashCommands: no
|
||
mentions: yes
|
||
addReactions: no
|
||
removeReactions: no
|
||
typingIndicator: yes
|
||
messageUpdatedEvents: no
|
||
messageDeletedEvents: no
|
||
directMessages: yes
|
||
ephemeralMessages: no
|
||
userLookup: no
|
||
customApiEndpoint: no
|
||
fetchMessages: no
|
||
fetchSingleMessage: no
|
||
fetchThreadInfo: yes
|
||
fetchChannelMessages: no
|
||
listThreads: no
|
||
fetchChannelInfo: yes
|
||
postChannelMessage: yes
|
||
---
|
||
|
||
## Install
|
||
|
||
<PackageInstall package="chat-adapter-zalo chat @chat-adapter/shared" />
|
||
|
||
## Quick start
|
||
|
||
```typescript title="lib/bot.ts" lineNumbers
|
||
import { Chat } from "chat";
|
||
import { createZaloAdapter } from "chat-adapter-zalo";
|
||
|
||
const bot = new Chat({
|
||
userName: "mybot",
|
||
adapters: {
|
||
zalo: createZaloAdapter(),
|
||
},
|
||
});
|
||
|
||
bot.onNewMention(async (thread, message) => {
|
||
await thread.post(`You said: ${message.text}`);
|
||
});
|
||
```
|
||
|
||
When called with no arguments, `createZaloAdapter()` reads its credentials from environment variables.
|
||
|
||
## Zalo Bot setup
|
||
|
||
### 1. Create a Zalo Bot
|
||
|
||
1. Go to [bot.zapps.me](https://bot.zapps.me) and sign in with your Zalo account.
|
||
2. Create a new bot and copy your **Bot Token** (format: `12345689:abc-xyz`).
|
||
3. In the **Webhooks** section, set your webhook URL.
|
||
|
||
### 2. Configure webhooks
|
||
|
||
1. In the Zalo Bot dashboard, navigate to **Webhooks**.
|
||
2. Set **Webhook URL** to `https://your-domain.com/api/webhooks/zalo`.
|
||
3. Set a **Secret Token** of your choice (8–256 characters) — this becomes `ZALO_WEBHOOK_SECRET`.
|
||
4. Subscribe to the message events you need (`message.text.received`, `message.image.received`, etc.).
|
||
|
||
### 3. Get credentials
|
||
|
||
Copy these values from the dashboard:
|
||
|
||
- **Bot Token** → `ZALO_BOT_TOKEN`
|
||
- The **Secret Token** you set in the webhook config → `ZALO_WEBHOOK_SECRET`
|
||
|
||
## Configuration
|
||
|
||
All options are auto-detected from environment variables when not provided.
|
||
|
||
<TypeTable
|
||
type={{
|
||
botToken: {
|
||
type: "string",
|
||
description:
|
||
"Zalo bot token. Auto-detected from `ZALO_BOT_TOKEN`. Required at runtime.",
|
||
},
|
||
webhookSecret: {
|
||
type: "string",
|
||
description:
|
||
"Secret token for webhook verification. Auto-detected from `ZALO_WEBHOOK_SECRET`. Required at runtime.",
|
||
},
|
||
userName: {
|
||
type: "string",
|
||
default: '"zalo-bot"',
|
||
description:
|
||
"Bot display name. Auto-detected from `ZALO_BOT_USERNAME` when omitted.",
|
||
},
|
||
logger: {
|
||
type: "Logger",
|
||
default: 'ConsoleLogger("info")',
|
||
description: "Logger instance.",
|
||
},
|
||
}}
|
||
/>
|
||
|
||
## Environment variables
|
||
|
||
```bash title=".env.local"
|
||
ZALO_BOT_TOKEN=12345689:abc-xyz # Bot token from Zalo Bot dashboard
|
||
ZALO_WEBHOOK_SECRET=your-secret # Secret token for X-Bot-Api-Secret-Token verification
|
||
ZALO_BOT_USERNAME=mybot # Optional, defaults to "zalo-bot"
|
||
```
|
||
|
||
## Webhook setup
|
||
|
||
```typescript title="app/api/webhooks/zalo/route.ts" lineNumbers
|
||
import { bot } from "@/lib/bot";
|
||
|
||
export async function POST(request: Request) {
|
||
return bot.webhooks.zalo(request);
|
||
}
|
||
```
|
||
|
||
Zalo delivers all events via POST requests with an `X-Bot-Api-Secret-Token` header. The adapter verifies this header using timing-safe comparison before processing any payload.
|
||
|
||
## Thread ID format
|
||
|
||
```
|
||
zalo:{chatId}
|
||
```
|
||
|
||
Example: `zalo:1234567890`
|
||
|
||
The `chatId` is the conversation ID from the Zalo webhook payload. For group chats it is the group ID; for private chats it is the user ID.
|
||
|
||
## Notes
|
||
|
||
- Zalo does not expose message history APIs to bots — `fetchMessages` returns an empty array.
|
||
- All formatting (bold, italic, code blocks) is stripped to plain text; Zalo renders no markdown.
|
||
- The bot token is embedded in the API URL path and is never logged.
|
||
- `isDM()` always returns `true` — Zalo thread IDs do not encode chat type.
|
||
- Streaming is buffered: tokens accumulate and the adapter auto-chunks at 2000 characters.
|
||
|
||
## Troubleshooting
|
||
|
||
### Webhook verification failing
|
||
|
||
- Confirm `ZALO_WEBHOOK_SECRET` matches the value you entered in the Zalo Bot dashboard.
|
||
- The adapter compares the `X-Bot-Api-Secret-Token` header using a timing-safe byte comparison — ensure the secret contains only ASCII characters and has no trailing whitespace.
|
||
|
||
### Messages not arriving
|
||
|
||
- Verify your webhook URL is reachable and returns `200 OK`.
|
||
- Check that the event types you need are subscribed in the Zalo Bot dashboard.
|
||
|
||
### "Zalo API error" on send
|
||
|
||
- Confirm `ZALO_BOT_TOKEN` is correct — it should be in `12345689:abc-xyz` format.
|
||
- The adapter calls `getMe` during `initialize()` to validate the token; check logs for initialization errors.
|
||
|
||
## Feature support
|
||
|
||
<FeatureSupport />
|