# Tài Liệu Hướng Dẫn Sử Dụng Zalo Personal API

## Giới Thiệu

**Zalo Personal API** là một thư viện JavaScript/TypeScript không chính thức cho phép tương tác với tài khoản Zalo cá nhân thông qua việc mô phỏng trình duyệt web. Thư viện cung cấp giao diện lập trình toàn diện để gửi tin nhắn, lắng nghe sự kiện, và quản lý các tính năng Zalo.

> ⚠️ **CẢNH BÁO**: Đây là API không chính thức. Việc sử dụng có thể dẫn đến tài khoản bị khóa hoặc cấm. Sử dụng với trách nhiệm của bạn.

## Cài Đặt

```bash
npm install zalo-personal
# hoặc
yarn add zalo-personal
# hoặc
bun install zalo-personal
```

## Yêu Cầu Hệ Thống

- Node.js >= 18.0.0
- TypeScript (khuyến nghị)

## Đăng Nhập - Hướng Dẫn Chi Tiết

### 1. Đăng Nhập Bằng QR Code (Khuyến Nghị)

Đây là phương pháp đăng nhập đơn giản và an toàn nhất:

```typescript
import { Zalo, LoginQRCallbackEventType } from "zalo-personal";

const zalo = new Zalo({
    selfListen: true,  // Lắng nghe tin nhắn từ chính mình
    logging: true      // Bật log để debug
});

// Đăng nhập đơn giản
const api = await zalo.loginQR();

// Hoặc đăng nhập với tùy chọn nâng cao
const api = await zalo.loginQR(
    {
        userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        language: "vi",
        qrPath: "./qr-code.png"  // Đường dẫn lưu QR code
    },
    (event) => {
        switch (event.type) {
            case LoginQRCallbackEventType.QRCodeGenerated:
                console.log("QR Code đã được tạo!");
                console.log("Mã QR:", event.data.code);
                // Tự động lưu QR code
                event.actions.saveToFile("./my-qr.png");
                break;
                
            case LoginQRCallbackEventType.QRCodeScanned:
                console.log("QR Code đã được quét bởi:", event.data.display_name);
                break;
                
            case LoginQRCallbackEventType.QRCodeExpired:
                console.log("QR Code đã hết hạn, đang tạo mới...");
                event.actions.retry();
                break;
                
            case LoginQRCallbackEventType.QRCodeDeclined:
                console.log("Đăng nhập bị từ chối");
                break;
                
            case LoginQRCallbackEventType.GotLoginInfo:
                console.log("Đã lấy được thông tin đăng nhập!");
                console.log("IMEI:", event.data.imei);
                console.log("User Agent:", event.data.userAgent);
                // Lưu thông tin này để đăng nhập lần sau
                break;
        }
    }
);

console.log("Đăng nhập thành công!");
```

### 2. Đăng Nhập Bằng Credentials (Nâng Cao)

Sử dụng thông tin đã lưu từ lần đăng nhập QR trước đó:

```typescript
import { Zalo, type Credentials } from "zalo-personal";

// Thông tin credentials từ lần đăng nhập QR trước
const credentials: Credentials = {
    imei: "your-imei-here",
    userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    language: "vi",
    cookie: [
        // Mảng cookie từ lần đăng nhập trước
        {
            domain: "chat.zalo.me",
            name: "cookie_name",
            value: "cookie_value",
            // ... các thuộc tính khác
        }
    ]
};

const zalo = new Zalo({
    selfListen: true,
    logging: true
});

try {
    const api = await zalo.login(credentials);
    console.log("Đăng nhập thành công với credentials!");
} catch (error) {
    console.error("Đăng nhập thất bại:", error);
    // Nếu credentials hết hạn, sử dụng QR code
    const api = await zalo.loginQR();
}
```

### 3. Lưu Và Tái Sử Dụng Credentials

```typescript
import fs from 'fs/promises';

// Lưu credentials sau khi đăng nhập QR thành công
const api = await zalo.loginQR({}, (event) => {
    if (event.type === LoginQRCallbackEventType.GotLoginInfo) {
        const credentials = {
            imei: event.data.imei,
            userAgent: event.data.userAgent,
            cookie: event.data.cookie,
            language: "vi"
        };
        
        // Lưu vào file
        fs.writeFile('credentials.json', JSON.stringify(credentials, null, 2));
        console.log("Đã lưu credentials vào credentials.json");
    }
});

// Đọc và sử dụng credentials đã lưu
async function loginWithSavedCredentials() {
    try {
        const credentialsData = await fs.readFile('credentials.json', 'utf8');
        const credentials = JSON.parse(credentialsData);
        
        const api = await zalo.login(credentials);
        return api;
    } catch (error) {
        console.log("Không thể đăng nhập với credentials đã lưu, sử dụng QR code...");
        return await zalo.loginQR();
    }
}
```

## Cấu Hình Tùy Chọn

```typescript
const zalo = new Zalo({
    selfListen: true,        // Lắng nghe tin nhắn từ chính mình
    logging: true,           // Bật logging
    apiType: "web",          // Loại API (mặc định: "web")
    apiVersion: "1.0",       // Phiên bản API
    agent: proxyAgent        // Proxy agent (tùy chọn)
});
```

## Xử Lý Lỗi Đăng Nhập

```typescript
import { ZaloApiError } from "zalo-personal";

async function safeLogin() {
    try {
        const api = await zalo.loginQR();
        return api;
    } catch (error) {
        if (error instanceof ZaloApiError) {
            switch (error.message) {
                case "Đăng nhập thất bại":
                    console.error("Lỗi xác thực, kiểm tra lại thông tin");
                    break;
                case "Unable to login with QRCode":
                    console.error("Không thể đăng nhập bằng QR code");
                    break;
                default:
                    console.error("Lỗi API:", error.message);
            }
        } else {
            console.error("Lỗi không xác định:", error);
        }
        throw error;
    }
}
```

## Lắng Nghe Sự Kiện

Sau khi đăng nhập thành công, bạn có thể lắng nghe các sự kiện:

```typescript
const { listener } = api;

// Sự kiện kết nối
listener.on("connected", () => {
    console.log("Đã kết nối WebSocket");
});

// Sự kiện ngắt kết nối
listener.on("disconnected", (reason) => {
    console.log("Ngắt kết nối:", reason);
});

// Sự kiện lỗi
listener.on("error", (error) => {
    console.error("Lỗi listener:", error);
});

// Bắt đầu lắng nghe
listener.start();
```

## Gửi Tin Nhắn Cơ Bản

```typescript
import { ThreadType } from "zalo-personal";

// Gửi tin nhắn văn bản đơn giản
await api.sendMessage("Xin chào!", "user_id_or_group_id", ThreadType.User);

// Gửi tin nhắn với tùy chọn nâng cao
await api.sendMessage({
    msg: "Tin nhắn với nhiều tính năng",
    quote: previousMessage,  // Trả lời tin nhắn
    attachments: ["./image.jpg"],  // Đính kèm file
    mentions: [{ uid: "user_id", pos: 0, len: 5 }]  // Tag người dùng
}, "thread_id", ThreadType.Group);
```

## Lưu Ý Quan Trọng

1. **Giới hạn kết nối**: Chỉ có thể có một kết nối WebSocket cho mỗi tài khoản
2. **Bảo mật**: Không chia sẻ credentials với người khác
3. **Rate limiting**: Tránh gửi quá nhiều tin nhắn trong thời gian ngắn
4. **Cập nhật**: Thư viện có thể cần cập nhật khi Zalo thay đổi API

## Lắng Nghe Tin Nhắn

### Lắng Nghe Tin Nhắn Mới

```typescript
import { ThreadType } from "zalo-personal";

api.listener.on("message", (message) => {
    const isPlainText = typeof message.data.content === "string";

    console.log("Nhận tin nhắn từ:", message.data.dName);
    console.log("Nội dung:", message.data.content);
    console.log("Thread ID:", message.threadId);
    console.log("Là tin nhắn của mình:", message.isSelf);

    switch (message.type) {
        case ThreadType.User:
            if (isPlainText && !message.isSelf) {
                console.log("Tin nhắn riêng:", message.data.content);
                // Tự động trả lời
                api.sendMessage("Đã nhận tin nhắn của bạn!", message.threadId, ThreadType.User);
            }
            break;

        case ThreadType.Group:
            if (isPlainText && !message.isSelf) {
                console.log("Tin nhắn nhóm:", message.data.content);
                // Chỉ trả lời khi được tag
                if (message.data.content.includes("@bot")) {
                    api.sendMessage("Xin chào nhóm!", message.threadId, ThreadType.Group);
                }
            }
            break;
    }
});
```

### Lắng Nghe Các Sự Kiện Khác

```typescript
// Lắng nghe reaction
api.listener.on("reaction", (reaction) => {
    console.log("Có người react:", reaction.data.react);
    console.log("Tin nhắn được react:", reaction.data.msgId);
});

// Lắng nghe typing
api.listener.on("typing", (typing) => {
    console.log("Có người đang gõ:", typing.data.userId);
});

// Lắng nghe tin nhắn đã xem
api.listener.on("seen_messages", (seenMessages) => {
    seenMessages.forEach(seen => {
        console.log("Tin nhắn đã được xem:", seen.data.msgId);
    });
});

// Lắng nghe sự kiện nhóm
api.listener.on("group_event", (groupEvent) => {
    console.log("Sự kiện nhóm:", groupEvent.type);
    console.log("Dữ liệu:", groupEvent.data);
});

// Lắng nghe sự kiện bạn bè
api.listener.on("friend_event", (friendEvent) => {
    console.log("Sự kiện bạn bè:", friendEvent.type);
    console.log("Dữ liệu:", friendEvent.data);
});
```

## Gửi Tin Nhắn

### Gửi Tin Nhắn Văn Bản

```typescript
// Gửi tin nhắn đơn giản
await api.sendMessage("Xin chào!", "user_id", ThreadType.User);

// Gửi tin nhắn với định dạng
await api.sendMessage({
    msg: "Tin nhắn có định dạng",
    styles: [
        { type: "bold", start: 0, end: 8 },      // In đậm
        { type: "italic", start: 9, end: 15 }    // In nghiêng
    ]
}, "user_id", ThreadType.User);

// Gửi tin nhắn khẩn cấp
await api.sendMessage({
    msg: "Tin nhắn khẩn cấp!",
    urgency: "high"
}, "user_id", ThreadType.User);
```

### Trả Lời Tin Nhắn

```typescript
api.listener.on("message", async (message) => {
    if (!message.isSelf && typeof message.data.content === "string") {
        // Trả lời tin nhắn
        await api.sendMessage({
            msg: "Đây là tin nhắn trả lời",
            quote: message  // Trích dẫn tin nhắn gốc
        }, message.threadId, message.type);
    }
});
```

### Gửi File Đính Kèm

```typescript
// Gửi hình ảnh
await api.sendMessage({
    msg: "Gửi hình ảnh",
    attachments: ["./image.jpg", "./image2.png"]
}, "user_id", ThreadType.User);

// Gửi video
await api.sendVideo({
    filePath: "./video.mp4",
    message: "Video hay nhé!"
}, "user_id", ThreadType.User);

// Gửi voice message
await api.sendVoice({
    filePath: "./audio.mp3"
}, "user_id", ThreadType.User);
```

### Gửi Sticker

```typescript
// Tìm sticker theo từ khóa
const stickerIds = await api.getStickers("hello");
if (stickerIds.length > 0) {
    const stickerDetails = await api.getStickersDetail(stickerIds[0]);
    await api.sendSticker(stickerDetails[0], "user_id", ThreadType.User);
}
```

### Mention Người Dùng (Trong Nhóm)

```typescript
await api.sendMessage({
    msg: "@user Xin chào bạn!",
    mentions: [
        {
            uid: "user_id",
            pos: 0,    // Vị trí bắt đầu mention
            len: 5,    // Độ dài mention
            type: 0    // Loại mention
        }
    ]
}, "group_id", ThreadType.Group);
```

## Quản Lý Bạn Bè và Nhóm

### Quản Lý Bạn Bè

```typescript
// Lấy danh sách bạn bè
const friends = await api.getAllFriends();
console.log("Danh sách bạn bè:", friends);

// Tìm kiếm người dùng
const searchResult = await api.findUser("tên hoặc số điện thoại");
console.log("Kết quả tìm kiếm:", searchResult);

// Gửi lời mời kết bạn
await api.sendFriendRequest("user_id", "Xin chào, kết bạn nhé!");

// Chấp nhận lời mời kết bạn
await api.acceptFriendRequest("user_id");

// Xóa bạn
await api.removeFriend("user_id");

// Chặn người dùng
await api.blockUser("user_id");

// Bỏ chặn người dùng
await api.unblockUser("user_id");
```

### Quản Lý Nhóm

```typescript
// Lấy danh sách nhóm
const groups = await api.getAllGroups();
console.log("Danh sách nhóm:", groups);

// Tạo nhóm mới
const newGroup = await api.createGroup({
    name: "Tên nhóm mới",
    members: ["user_id_1", "user_id_2"]
});

// Lấy thông tin nhóm
const groupInfo = await api.getGroupInfo("group_id");
console.log("Thông tin nhóm:", groupInfo);

// Thêm thành viên vào nhóm
await api.addUserToGroup("user_id", "group_id");

// Xóa thành viên khỏi nhóm
await api.removeUserFromGroup("user_id", "group_id");

// Đổi tên nhóm
await api.changeGroupName("group_id", "Tên mới");

// Đổi avatar nhóm
await api.changeGroupAvatar("group_id", "./avatar.jpg");

// Rời nhóm
await api.leaveGroup("group_id");

// Giải tán nhóm (chỉ admin)
await api.disperseGroup("group_id");
```

## Tính Năng Nâng Cao

### Reaction

```typescript
import { Reactions } from "zalo-personal";

// Thêm reaction vào tin nhắn
await api.addReaction(Reactions.LIKE, message);
await api.addReaction(Reactions.LOVE, message);
await api.addReaction(Reactions.HAHA, message);
await api.addReaction(Reactions.WOW, message);
await api.addReaction(Reactions.SAD, message);
await api.addReaction(Reactions.ANGRY, message);

// Reaction tùy chỉnh
await api.addReaction({
    icon: "👍",
    message: message
});
```

### Quản Lý Tin Nhắn

```typescript
// Xóa tin nhắn
await api.deleteMessage("message_id", "thread_id", ThreadType.User);

// Chuyển tiếp tin nhắn
await api.forwardMessage({
    message: message,
    targets: [
        { threadId: "user_id_1", type: ThreadType.User },
        { threadId: "group_id_1", type: ThreadType.Group }
    ]
});

// Đánh dấu tin nhắn chưa đọc
await api.addUnreadMark("thread_id", ThreadType.User);

// Xóa đánh dấu chưa đọc
await api.removeUnreadMark("thread_id", ThreadType.User);
```

### Cài Đặt Tài Khoản

```typescript
// Cập nhật thông tin cá nhân
await api.updateProfile({
    displayName: "Tên hiển thị mới",
    birthday: "1990-01-01",
    gender: 1  // 1: Nam, 2: Nữ
});

// Đổi avatar
await api.changeAccountAvatar("./new_avatar.jpg");

// Cập nhật ngôn ngữ
await api.updateLang("en");  // "vi", "en", etc.
```

### Tính Năng Khác

```typescript
// Gửi typing indicator
await api.sendTypingEvent("user_id", ThreadType.User);

// Đánh dấu đã xem tin nhắn
await api.sendSeenEvent("message_id", "thread_id", ThreadType.User);

// Tạo poll trong nhóm
await api.createPoll({
    question: "Câu hỏi poll",
    options: ["Tùy chọn 1", "Tùy chọn 2", "Tùy chọn 3"],
    allowMultipleChoice: false
}, "group_id");

// Tạo reminder
await api.createReminder({
    message: "Nhắc nhở",
    time: new Date(Date.now() + 3600000), // 1 giờ sau
    targets: ["user_id_1", "user_id_2"]
});
```

## Xử Lý Lỗi và Debug

```typescript
import { ZaloApiError } from "zalo-personal";

try {
    await api.sendMessage("Test", "invalid_user_id");
} catch (error) {
    if (error instanceof ZaloApiError) {
        console.error("Lỗi API Zalo:", error.message);

        // Xử lý các loại lỗi cụ thể
        switch (error.message) {
            case "Missing threadId":
                console.error("Thiếu ID thread");
                break;
            case "Failed to encrypt message":
                console.error("Lỗi mã hóa tin nhắn");
                break;
            default:
                console.error("Lỗi không xác định:", error.message);
        }
    } else {
        console.error("Lỗi hệ thống:", error);
    }
}

// Bật logging để debug
const zalo = new Zalo({
    logging: true,
    selfListen: true
});
```

## Best Practices

### 1. Quản Lý Kết Nối

```typescript
// Tự động kết nối lại khi bị ngắt
api.listener.on("disconnected", (reason) => {
    console.log("Ngắt kết nối:", reason);

    // Kết nối lại sau 5 giây
    setTimeout(() => {
        api.listener.start({ retryOnClose: true });
    }, 5000);
});

// Giữ kết nối hoạt động
setInterval(async () => {
    try {
        await api.keepAlive();
    } catch (error) {
        console.error("Lỗi keep alive:", error);
    }
}, 300000); // 5 phút
```

### 2. Rate Limiting

```typescript
class MessageQueue {
    private queue: Array<() => Promise<any>> = [];
    private processing = false;
    private delay = 1000; // 1 giây giữa các tin nhắn

    async add(fn: () => Promise<any>) {
        this.queue.push(fn);
        if (!this.processing) {
            this.process();
        }
    }

    private async process() {
        this.processing = true;

        while (this.queue.length > 0) {
            const fn = this.queue.shift()!;
            try {
                await fn();
            } catch (error) {
                console.error("Lỗi gửi tin nhắn:", error);
            }
            await new Promise(resolve => setTimeout(resolve, this.delay));
        }

        this.processing = false;
    }
}

const messageQueue = new MessageQueue();

// Sử dụng queue để gửi tin nhắn
messageQueue.add(() => api.sendMessage("Tin nhắn 1", "user_id"));
messageQueue.add(() => api.sendMessage("Tin nhắn 2", "user_id"));
```

### 3. Lưu Trữ Dữ Liệu

```typescript
import fs from 'fs/promises';

class DataManager {
    private dataFile = './bot_data.json';
    private data: any = {};

    async load() {
        try {
            const content = await fs.readFile(this.dataFile, 'utf8');
            this.data = JSON.parse(content);
        } catch (error) {
            this.data = {};
        }
    }

    async save() {
        await fs.writeFile(this.dataFile, JSON.stringify(this.data, null, 2));
    }

    get(key: string) {
        return this.data[key];
    }

    set(key: string, value: any) {
        this.data[key] = value;
        this.save(); // Tự động lưu
    }
}

const dataManager = new DataManager();
await dataManager.load();

// Lưu thông tin người dùng
api.listener.on("message", (message) => {
    const userData = dataManager.get(`user_${message.data.uidFrom}`) || {};
    userData.lastMessage = message.data.content;
    userData.lastSeen = new Date().toISOString();
    dataManager.set(`user_${message.data.uidFrom}`, userData);
});
```

## Ví Dụ Chatbot Hoàn Chỉnh

```typescript
import { Zalo, ThreadType, LoginQRCallbackEventType } from "zalo-personal";

class ZaloChatBot {
    private api: any;
    private commands: Map<string, (message: any, args: string[]) => Promise<void>>;

    constructor() {
        this.commands = new Map();
        this.setupCommands();
    }

    private setupCommands() {
        this.commands.set("ping", this.pingCommand.bind(this));
        this.commands.set("time", this.timeCommand.bind(this));
        this.commands.set("weather", this.weatherCommand.bind(this));
        this.commands.set("help", this.helpCommand.bind(this));
    }

    async start() {
        const zalo = new Zalo({
            selfListen: false,
            logging: true
        });

        // Đăng nhập
        this.api = await zalo.loginQR({}, (event) => {
            if (event.type === LoginQRCallbackEventType.QRCodeGenerated) {
                console.log("Quét QR code để đăng nhập bot");
                event.actions.saveToFile("./bot-qr.png");
            }
        });

        // Thiết lập listener
        this.setupListeners();

        console.log("Bot đã sẵn sàng!");
    }

    private setupListeners() {
        this.api.listener.on("message", this.handleMessage.bind(this));

        this.api.listener.on("connected", () => {
            console.log("Bot đã kết nối");
        });

        this.api.listener.on("error", (error: any) => {
            console.error("Lỗi bot:", error);
        });

        this.api.listener.start({ retryOnClose: true });
    }

    private async handleMessage(message: any) {
        if (message.isSelf || typeof message.data.content !== "string") return;

        const content = message.data.content.trim();
        if (!content.startsWith("/")) return;

        const [command, ...args] = content.slice(1).split(" ");
        const commandHandler = this.commands.get(command.toLowerCase());

        if (commandHandler) {
            try {
                await commandHandler(message, args);
            } catch (error) {
                console.error(`Lỗi command ${command}:`, error);
                await this.api.sendMessage(
                    "Có lỗi xảy ra khi xử lý lệnh.",
                    message.threadId,
                    message.type
                );
            }
        }
    }

    private async pingCommand(message: any) {
        await this.api.sendMessage("Pong! 🏓", message.threadId, message.type);
    }

    private async timeCommand(message: any) {
        const now = new Date().toLocaleString("vi-VN");
        await this.api.sendMessage(`Thời gian hiện tại: ${now}`, message.threadId, message.type);
    }

    private async weatherCommand(message: any, args: string[]) {
        const city = args.join(" ") || "Hà Nội";
        // Tích hợp API thời tiết thực tế ở đây
        await this.api.sendMessage(
            `Thời tiết tại ${city}: Nắng, 25°C ☀️`,
            message.threadId,
            message.type
        );
    }

    private async helpCommand(message: any) {
        const helpText = `
🤖 Danh sách lệnh:
/ping - Kiểm tra bot
/time - Xem thời gian
/weather [thành phố] - Xem thời tiết
/help - Hiển thị trợ giúp
        `.trim();

        await this.api.sendMessage(helpText, message.threadId, message.type);
    }
}

// Khởi chạy bot
const bot = new ZaloChatBot();
bot.start().catch(console.error);
```

## Kết Luận

Thư viện **Zalo Personal API** cung cấp một bộ công cụ mạnh mẽ để tương tác với Zalo. Với hướng dẫn chi tiết này, bạn có thể:

1. **Đăng nhập an toàn** bằng QR code hoặc credentials
2. **Lắng nghe và xử lý** tin nhắn, sự kiện
3. **Gửi tin nhắn đa dạng** với nhiều định dạng
4. **Quản lý bạn bè và nhóm** hiệu quả
5. **Xây dựng chatbot** hoàn chỉnh

Hãy nhớ sử dụng thư viện một cách có trách nhiệm và tuân thủ các quy định của Zalo để tránh bị khóa tài khoản.

---

📚 **Tài liệu này được cập nhật thường xuyên. Hãy kiểm tra phiên bản mới nhất trên GitHub.**
