@zca-mt/zca
v1.2.2
Published
Unofficial Zalo API for JavaScript and TypeScript
Maintainers
Readme
ZCA-MT
Unofficial Zalo API for JavaScript & TypeScript
Automate a personal Zalo account from Node.js: sign in with a QR code, receive real-time events, and send messages, images, files, stickers, video, and voice using ZCA-MT.
Getting started · Usage · API overview · Troubleshooting
[!WARNING] ZCA-MT is an unofficial API and is not affiliated with or endorsed by Zalo. It interacts with Zalo Web and may stop working when Zalo changes its system. Using an unofficial client may cause account restrictions. Only use accounts and conversations you are authorized to manage. Do not use this package for spam, harassment, or unsolicited bulk messaging.
✨ Features
- QR-code and session-based login
- Real-time messages, reactions, typing, group events, and friend events
- Text, image, file, sticker, video, and voice messages
- User, friend, group, reminder, poll, catalog, and conversation APIs
- First-class TypeScript declarations
- ESM and CommonJS builds
- Optional local session persistence
📋 Requirements
- Node.js 20 or newer
- npm or another compatible package manager
🚀 Getting started
Install
npm install @zca-mt/zcaLog in with a QR code
import { ZcaMT } from "@zca-mt/zca";
const zca = new ZcaMT();
const api = await zca.loginQR();
console.log("Logged in as:", await api.fetchAccountInfo());The QR code is shown locally in your terminal or written to the optional path you provide. ZCA-MT does not upload it or print your cookies and tokens.
Configuration
const zca = new ZcaMT({
selfListen: false, // Ignore messages sent by the logged-in account
checkUpdate: true, // Check for known incompatible client versions
logging: true, // Enable redacted ZCA-MT logs
});💡 Usage
Listen and reply to messages
import { ZcaMT } from "@zca-mt/zca";
const zca = new ZcaMT();
const api = await zca.loginQR();
api.listener.on("message", async (message) => {
if (message.isSelf) return;
const content = message.data.content;
if (typeof content !== "string") return;
console.log({
threadId: message.threadId,
threadType: message.type,
content,
});
await api.sendMessage(
{ msg: `You sent: ${content}` },
message.threadId,
message.type,
);
});
api.listener.start();Listener events include connected, disconnected, closed, error,
message, typing, reaction, group_event, and friend_event. See
src/apis/listen.ts for the current event surface.
Send a text message
import { ThreadType } from "@zca-mt/zca";
await api.sendMessage(
{ msg: "Hello from ZCA-MT!" },
threadId,
ThreadType.User,
);Use ThreadType.Group when the target is a group conversation.
Reply to a message
await api.sendMessage(
{
msg: "This is a reply",
quote: originalMessage.data,
},
originalMessage.threadId,
originalMessage.type,
);Send an image
Zalo requires image width, height, and size metadata. Install an image library
such as sharp when needed:
npm install sharpimport fs from "node:fs";
import sharp from "sharp";
import { ThreadType, withImageMetadataValidation, ZcaMT } from "@zca-mt/zca";
async function imageMetadataGetter(filePath: string) {
const data = await fs.promises.readFile(filePath);
const metadata = await sharp(data).metadata();
return {
width: metadata.width,
height: metadata.height,
size: metadata.size ?? data.length,
};
}
const zca = new ZcaMT({
imageMetadataGetter: withImageMetadataValidation(imageMetadataGetter),
});
const api = await zca.loginQR();
await api.sendMessage(
{ msg: "Photo attachment", attachments: "./photo.jpg" },
threadId,
ThreadType.User,
);sharp is optional and is not included in the ZCA-MT dependencies.
Stop the listener safely
function shutdown(signal: string) {
console.log(`Received ${signal}. Stopping ZCA-MT...`);
api.listener.stop();
process.exitCode = 0;
}
process.once("SIGINT", () => shutdown("SIGINT"));
process.once("SIGTERM", () => shutdown("SIGTERM"));🧩 API overview
| Area | Example |
| --- | --- |
| QR login | zca.loginQR() |
| Cookie/session login | zca.login(credentials) |
| Account information | api.fetchAccountInfo() |
| Message listener | api.listener.on("message", handler) |
| Group and friend events | group_event, friend_event |
| Messages and attachments | api.sendMessage(...) |
| Stickers | api.sendSticker(...) |
| Video and voice | api.sendVideo(...), api.sendVoice(...) |
| User information | api.getUserInfo(...) |
| Group information | api.getGroupInfo(...) |
| Listener lifecycle | api.listener.start(), api.listener.stop() |
For the complete method and type list, browse src/apis and
index.d.ts.
🧩 Full bot modules
ZCA-MT includes a lightweight module layer for building bots with isolated, controllable runtime features:
ConfigStore: runtime settings and feature flagsAutoReplyEngine: deterministic keyword-based repliesDashboard: account status and message countersReconnectController: bounded retry budgets and backoffModuleManager: named module registration and lifecycle control
Module example
import {
AutoReplyEngine,
ConfigStore,
Dashboard,
ModuleManager,
ReconnectController,
} from "@zca-mt/zca";
const config = new ConfigStore({
prefix: "!",
adminIds: ["admin-1"],
enabledModules: {
autoReply: true,
dashboard: true,
reconnect: true,
},
});
const autoReply = new AutoReplyEngine();
const dashboard = new Dashboard();
const reconnect = new ReconnectController({
maxAttempts: 5,
baseDelayMs: 500,
maxDelayMs: 10_000,
});
const manager = new ModuleManager();
autoReply.addRule({ keyword: "hello", response: "Hi from ZCA-MT!" });
manager.register({
name: "autoReply",
start: () => true,
stop: () => true,
});
manager.start("autoReply");
console.log(config.get("prefix"));
console.log(autoReply.process("hello there"));
console.log(dashboard.snapshot());
console.log(reconnect.state);For a complete login, listener, rate-limiting, auto-reply, reconnect, and
dashboard example, see examples/full-bot.ts.
The matching JSON configuration template is
examples/bot.config.json, and the module design
is documented in MODULES.md.
🔐 Session security
ZCA-MT does not persist login sessions unless your application explicitly does so. To reuse a session, use the provided helpers:
import { loadSession, saveSession, ZcaMT } from "@zca-mt/zca";
const sessionPath = "./.zca-mt/session.json";
const zca = new ZcaMT();
let api;
try {
api = await zca.login(loadSession(sessionPath));
} catch {
api = await zca.loginQR();
const context = api.getContext();
saveSession(sessionPath, {
imei: context.imei,
userAgent: context.userAgent,
cookie: context.cookie.toJSON()?.cookies ?? [],
});
}[!IMPORTANT] A session file is equivalent to a live login credential. Never share it, print it in logs, or commit it to Git. Keep
.zca-mt/,session.json, andcredentials.jsonin.gitignore.
🛠️ Development
git clone https://github.com/devminhtri15022/zca-mt.git
cd zca-mt
npm install
npm run check| Command | Purpose |
| --- | --- |
| npm run build | Build ESM and CommonJS outputs |
| npm run typecheck | Check TypeScript types |
| npm run lint | Run ESLint |
| npm run format:check | Check formatting |
| npm test | Run the test suite |
| npm run check | Run all checks and build |
❓ Troubleshooting
Run loginQR() again. QR codes are time-limited by Zalo.
Inspect the closed and error events. Some codes indicate another login or
a terminated session; in that case, authenticate again.
Provide imageMetadataGetter in the ZcaMT options. See the image example.
Reduce request frequency and avoid automated bulk sending. Account restrictions are an inherent risk of unofficial clients.
🤝 Contributing
Contributions are welcome. For substantial changes, open an issue first. Before
submitting a pull request, run npm run check.
Changes intended for spam, credential harvesting, or bypassing CAPTCHA, 2FA, and rate-limit protections will not be accepted.
📄 License
Distributed under the MIT License. See LICENSE.
