npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zca-mt/zca

v1.2.2

Published

Unofficial Zalo API for JavaScript and TypeScript

Readme

ZCA-MT

Unofficial Zalo API for JavaScript & TypeScript

npm Node.js TypeScript License

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/zca

Log 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 sharp
import 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 flags
  • AutoReplyEngine: deterministic keyword-based replies
  • Dashboard: account status and message counters
  • ReconnectController: bounded retry budgets and backoff
  • ModuleManager: 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, and credentials.json in .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.