@warriorteam/zalo-personal
Version:
Unofficial Zalo Personal API for JavaScript - A powerful library for interacting with Zalo personal accounts with URL attachment support
809 lines (643 loc) • 21.1 kB
Markdown
# API Gửi Tin Nhắn (Messaging API)
## Tổng Quan
Zalo Personal SDK cung cấp API mạnh mẽ để gửi nhiều loại tin nhắn khác nhau:
- Tin nhắn văn bản (có định dạng)
- Tin nhắn với file đính kèm
- Tin nhắn trả lời (quote)
- Tin nhắn có mention
- Tin nhắn tự hủy
- Sticker, link, card, voice, video
## Gửi Tin Nhắn Cơ Bản
### 1. Tin Nhắn Văn Bản Đơn Giản
```typescript
import { ThreadType } from 'zalo-personal-sdk';
// Gửi cho người dùng
await api.sendMessage('Xin chào!', 'user-id', ThreadType.User);
// Gửi cho nhóm
await api.sendMessage('Hello group!', 'group-id', ThreadType.Group);
// Sử dụng object
const message = {
msg: 'Tin nhắn từ object'
};
await api.sendMessage(message, 'user-id', ThreadType.User);
```
### 2. Kết Quả Trả Về
```typescript
const result = await api.sendMessage('Hello', 'user-id', ThreadType.User);
console.log(result);
// {
// message: { msgId: 123456789 },
// attachment: []
// }
```
## Định Dạng Văn Bản
### 1. Các Kiểu Định Dạng
```typescript
import { TextStyle, MessageContent } from 'zalo-personal-sdk';
const messageContent: MessageContent = {
msg: 'Văn bản có định dạng: in đậm, in nghiêng, gạch chân',
styles: [
{ start: 22, len: 7, st: TextStyle.Bold }, // "in đậm"
{ start: 31, len: 10, st: TextStyle.Italic }, // "in nghiêng"
{ start: 43, len: 9, st: TextStyle.Underline } // "gạch chân"
]
};
await api.sendMessage(messageContent, 'user-id', ThreadType.User);
```
### 2. Màu Sắc
```typescript
const colorMessage: MessageContent = {
msg: 'Màu đỏ, màu cam, màu vàng, màu xanh',
styles: [
{ start: 0, len: 7, st: TextStyle.Red }, // "Màu đỏ"
{ start: 9, len: 7, st: TextStyle.Orange }, // "màu cam"
{ start: 18, len: 8, st: TextStyle.Yellow }, // "màu vàng"
{ start: 28, len: 9, st: TextStyle.Green } // "màu xanh"
]
};
await api.sendMessage(colorMessage, 'user-id', ThreadType.User);
```
### 3. Kích Thước Font
```typescript
const sizeMessage: MessageContent = {
msg: 'Font nhỏ và Font lớn',
styles: [
{ start: 0, len: 8, st: TextStyle.Small }, // "Font nhỏ"
{ start: 12, len: 9, st: TextStyle.Big } // "Font lớn"
]
};
await api.sendMessage(sizeMessage, 'user-id', ThreadType.User);
```
### 4. Danh Sách
```typescript
const listMessage: MessageContent = {
msg: 'Item 1\nItem 2\nItem 3',
styles: [
{ start: 0, len: 6, st: TextStyle.UnorderedList }, // "Item 1"
{ start: 7, len: 6, st: TextStyle.UnorderedList }, // "Item 2"
{ start: 14, len: 6, st: TextStyle.UnorderedList } // "Item 3"
]
};
await api.sendMessage(listMessage, 'user-id', ThreadType.User);
```
### 5. Thụt Lề
```typescript
const indentMessage: MessageContent = {
msg: 'Đoạn thụt lề với khoảng cách tùy chỉnh',
styles: [
{
start: 0,
len: 35,
st: TextStyle.Indent,
indentSize: 2 // Thụt lề 20px (2 * 10px)
}
]
};
await api.sendMessage(indentMessage, 'user-id', ThreadType.User);
```
### 6. Kết Hợp Nhiều Định Dạng
```typescript
const complexMessage: MessageContent = {
msg: 'Văn bản phức tạp với nhiều định dạng',
styles: [
{ start: 0, len: 8, st: TextStyle.Bold },
{ start: 0, len: 8, st: TextStyle.Red },
{ start: 9, len: 8, st: TextStyle.Italic },
{ start: 9, len: 8, st: TextStyle.Underline }
]
};
await api.sendMessage(complexMessage, 'user-id', ThreadType.User);
```
## Mức Độ Ưu Tiên
```typescript
import { Urgency } from 'zalo-personal-sdk';
const urgentMessage: MessageContent = {
msg: 'Tin nhắn khẩn cấp!',
urgency: Urgency.Urgent
};
const importantMessage: MessageContent = {
msg: 'Tin nhắn quan trọng',
urgency: Urgency.Important
};
await api.sendMessage(urgentMessage, 'user-id', ThreadType.User);
await api.sendMessage(importantMessage, 'user-id', ThreadType.User);
```
## Mention (Tag Người Dùng)
### 1. Mention Trong Nhóm
```typescript
const mentionMessage: MessageContent = {
msg: '@John @Jane Chào các bạn!',
mentions: [
{ pos: 0, len: 5, uid: 'john-user-id' }, // "@John"
{ pos: 6, len: 5, uid: 'jane-user-id' } // "@Jane"
]
};
// Chỉ hoạt động trong nhóm
await api.sendMessage(mentionMessage, 'group-id', ThreadType.Group);
```
### 2. Mention All
```typescript
const mentionAllMessage: MessageContent = {
msg: '@all Thông báo quan trọng!',
mentions: [
{ pos: 0, len: 4, uid: '-1' } // "-1" là ID đặc biệt cho mention all
]
};
await api.sendMessage(mentionAllMessage, 'group-id', ThreadType.Group);
```
### 3. Helper Function Cho Mention
```typescript
function createMentionMessage(text: string, mentions: Array<{name: string, uid: string}>) {
let finalText = text;
const mentionArray: Mention[] = [];
mentions.forEach(mention => {
const mentionText = `@${mention.name}`;
const pos = finalText.indexOf(mentionText);
if (pos !== -1) {
mentionArray.push({
pos: pos,
len: mentionText.length,
uid: mention.uid
});
}
});
return {
msg: finalText,
mentions: mentionArray
};
}
// Sử dụng
const message = createMentionMessage(
'Xin chào @John và @Jane!',
[
{ name: 'John', uid: 'john-id' },
{ name: 'Jane', uid: 'jane-id' }
]
);
await api.sendMessage(message, 'group-id', ThreadType.Group);
```
## Trả Lời Tin Nhắn (Quote)
### 1. Quote Tin Nhắn Văn Bản
```typescript
// Tin nhắn gốc từ listener hoặc API khác
const originalMessage = {
content: 'Tin nhắn gốc cần được trả lời',
msgType: 'webchat',
propertyExt: {},
uidFrom: 'sender-id',
msgId: 123456,
cliMsgId: 'cli-msg-id',
ts: Date.now(),
ttl: 0
};
const replyMessage: MessageContent = {
msg: 'Đây là câu trả lời',
quote: originalMessage
};
await api.sendMessage(replyMessage, 'user-id', ThreadType.User);
```
### 2. Quote Với Định Dạng
```typescript
const styledReply: MessageContent = {
msg: 'Trả lời với định dạng đẹp',
quote: originalMessage,
styles: [
{ start: 8, len: 4, st: TextStyle.Bold }
]
};
await api.sendMessage(styledReply, 'user-id', ThreadType.User);
```
### 3. Quote Helper Function
```typescript
function createReplyMessage(replyText: string, originalMessage: any) {
return {
msg: replyText,
quote: {
content: originalMessage.content,
msgType: originalMessage.msgType,
propertyExt: originalMessage.propertyExt || {},
uidFrom: originalMessage.uidFrom,
msgId: originalMessage.msgId,
cliMsgId: originalMessage.cliMsgId,
ts: originalMessage.ts,
ttl: originalMessage.ttl || 0
}
};
}
// Sử dụng
const reply = createReplyMessage('Cảm ơn bạn!', receivedMessage);
await api.sendMessage(reply, threadId, threadType);
```
## Tin Nhắn Tự Hủy (TTL)
### 1. Cấu Hình TTL
```typescript
const selfDestructMessage: MessageContent = {
msg: 'Tin nhắn này sẽ tự hủy sau 60 giây',
ttl: 60 * 1000 // TTL tính bằng milliseconds
};
await api.sendMessage(selfDestructMessage, 'user-id', ThreadType.User);
```
### 2. Các Mức TTL Thông Dụng
```typescript
const TTL_PRESETS = {
ONE_MINUTE: 60 * 1000,
FIVE_MINUTES: 5 * 60 * 1000,
ONE_HOUR: 60 * 60 * 1000,
ONE_DAY: 24 * 60 * 60 * 1000,
ONE_WEEK: 7 * 24 * 60 * 60 * 1000
};
const quickMessage: MessageContent = {
msg: 'Tin nhắn tự hủy sau 5 phút',
ttl: TTL_PRESETS.FIVE_MINUTES
};
```
## File Đính Kèm
### 1. Gửi File Từ Đường Dẫn Local
```typescript
const imageMessage: MessageContent = {
msg: 'Ảnh đẹp không?',
attachments: '/path/to/image.jpg'
};
await api.sendMessage(imageMessage, 'user-id', ThreadType.User);
```
### 2. Gửi Nhiều File
```typescript
const multiFileMessage: MessageContent = {
msg: 'Gửi nhiều file cùng lúc',
attachments: [
'/path/to/image1.jpg',
'/path/to/image2.png',
'/path/to/document.pdf'
]
};
await api.sendMessage(multiFileMessage, 'user-id', ThreadType.User);
```
### 3. Gửi File Từ URL
```typescript
const urlFileMessage: MessageContent = {
msg: 'File từ internet',
attachments: {
url: 'https://example.com/image.jpg',
filename: 'downloaded-image.jpg',
headers: {
'User-Agent': 'MyBot/1.0'
}
}
};
await api.sendMessage(urlFileMessage, 'user-id', ThreadType.User);
```
### 4. Gửi File Từ Buffer
```typescript
import fs from 'fs';
const fileBuffer = fs.readFileSync('./image.jpg');
const bufferFileMessage: MessageContent = {
msg: 'File từ buffer',
attachments: {
data: fileBuffer,
filename: 'buffer-image.jpg',
metadata: {
totalSize: fileBuffer.length,
width: 1920, // Cho ảnh
height: 1080 // Cho ảnh
}
}
};
await api.sendMessage(bufferFileMessage, 'user-id', ThreadType.User);
```
### 5. Kiểm Tra File Trước Khi Gửi
```typescript
import fs from 'fs/promises';
import path from 'path';
async function validateAndSendFile(filePath: string, threadId: string, threadType: ThreadType) {
try {
// Kiểm tra file tồn tại
await fs.access(filePath);
// Kiểm tra kích thước file (max 25MB)
const stats = await fs.stat(filePath);
const maxSize = 25 * 1024 * 1024; // 25MB
if (stats.size > maxSize) {
throw new Error(`File quá lớn: ${(stats.size / (1024*1024)).toFixed(1)}MB > 25MB`);
}
// Kiểm tra loại file
const ext = path.extname(filePath).toLowerCase();
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.mp4', '.pdf', '.doc', '.docx'];
if (!allowedExtensions.includes(ext)) {
throw new Error(`Loại file không được hỗ trợ: ${ext}`);
}
// Gửi file
const message: MessageContent = {
msg: `Đã gửi file: ${path.basename(filePath)}`,
attachments: filePath
};
const result = await api.sendMessage(message, threadId, threadType);
console.log('✅ Gửi file thành công:', result);
return result;
} catch (error) {
console.error('❌ Lỗi gửi file:', error.message);
throw error;
}
}
```
## Các Loại Tin Nhắn Đặc Biệt
### 1. Sticker
```typescript
// Lấy danh sách sticker
const stickers = await api.getStickers();
console.log('Danh sách sticker:', stickers);
// Lấy chi tiết sticker category
const stickerDetail = await api.getStickersDetail('category-id');
// Gửi sticker
await api.sendSticker('sticker-id', 'user-id', ThreadType.User, 'category-id');
```
### 2. Link Preview
```typescript
const linkOptions = {
url: 'https://github.com/zalo-sdk/zalo-personal-sdk',
title: 'Zalo Personal SDK',
description: 'SDK unofficial cho Zalo Personal API',
thumbnail: 'https://github.com/image.jpg'
};
await api.sendLink(linkOptions, 'user-id', ThreadType.User);
```
### 3. Card/Danh Thiếp
```typescript
const cardOptions = {
userId: 'target-user-id' // ID người muốn chia sẻ thông tin
};
await api.sendCard(cardOptions, 'recipient-id', ThreadType.User);
```
### 4. Voice Message
```typescript
const voiceOptions = {
filePath: '/path/to/audio.m4a',
duration: 15 // Thời lượng tính bằng giây
};
await api.sendVoice(voiceOptions, 'user-id', ThreadType.User);
```
### 5. Video Message
```typescript
const videoOptions = {
filePath: '/path/to/video.mp4',
duration: 30, // Thời lượng (giây)
width: 1920, // Chiều rộng
height: 1080 // Chiều cao
};
await api.sendVideo(videoOptions, 'user-id', ThreadType.User);
```
## Xử Lý Lỗi
### 1. Try-Catch Pattern
```typescript
import { ZaloApiError } from 'zalo-personal-sdk';
try {
const result = await api.sendMessage('Hello', 'user-id', ThreadType.User);
console.log('✅ Gửi thành công:', result);
} catch (error) {
if (error instanceof ZaloApiError) {
switch (error.message) {
case 'Missing message content':
console.error('❌ Thiếu nội dung tin nhắn');
break;
case 'Missing threadId':
console.error('❌ Thiếu ID thread');
break;
case 'Exceed maximum file':
console.error('❌ Vượt quá số lượng file tối đa');
break;
default:
console.error('❌ Lỗi API:', error.message);
}
} else {
console.error('❌ Lỗi hệ thống:', error);
}
}
```
### 2. Retry Logic
```typescript
async function sendMessageWithRetry(
message: MessageContent | string,
threadId: string,
threadType: ThreadType,
maxRetries: number = 3
) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await api.sendMessage(message, threadId, threadType);
console.log(`✅ Gửi thành công lần thử ${attempt}`);
return result;
} catch (error) {
console.error(`❌ Lần thử ${attempt} thất bại:`, error.message);
if (attempt === maxRetries) {
throw new Error(`Gửi tin nhắn thất bại sau ${maxRetries} lần thử`);
}
// Chờ trước khi thử lại (exponential backoff)
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
```
### 3. Rate Limiting
```typescript
class MessageRateLimiter {
private messageQueue: Array<() => Promise<any>> = [];
private isProcessing = false;
private readonly delayBetweenMessages = 1000; // 1 giây
async sendMessage(
message: MessageContent | string,
threadId: string,
threadType: ThreadType
) {
return new Promise((resolve, reject) => {
const task = async () => {
try {
const result = await api.sendMessage(message, threadId, threadType);
resolve(result);
} catch (error) {
reject(error);
}
};
this.messageQueue.push(task);
this.processQueue();
});
}
private async processQueue() {
if (this.isProcessing || this.messageQueue.length === 0) return;
this.isProcessing = true;
while (this.messageQueue.length > 0) {
const task = this.messageQueue.shift()!;
await task();
// Delay giữa các tin nhắn
if (this.messageQueue.length > 0) {
await new Promise(resolve => setTimeout(resolve, this.delayBetweenMessages));
}
}
this.isProcessing = false;
}
}
// Sử dụng
const rateLimiter = new MessageRateLimiter();
await rateLimiter.sendMessage('Message 1', 'user-id', ThreadType.User);
await rateLimiter.sendMessage('Message 2', 'user-id', ThreadType.User);
await rateLimiter.sendMessage('Message 3', 'user-id', ThreadType.User);
```
## Utility Functions
### 1. Message Builder
```typescript
class MessageBuilder {
private message: MessageContent = { msg: '' };
text(text: string) {
this.message.msg = text;
return this;
}
style(start: number, length: number, style: TextStyle) {
if (!this.message.styles) this.message.styles = [];
this.message.styles.push({ start, len: length, st: style });
return this;
}
urgent() {
this.message.urgency = Urgency.Urgent;
return this;
}
important() {
this.message.urgency = Urgency.Important;
return this;
}
ttl(milliseconds: number) {
this.message.ttl = milliseconds;
return this;
}
attachment(source: AttachmentSource) {
if (!this.message.attachments) {
this.message.attachments = source;
} else if (Array.isArray(this.message.attachments)) {
this.message.attachments.push(source as any);
} else {
this.message.attachments = [this.message.attachments as any, source as any];
}
return this;
}
mention(pos: number, length: number, uid: string) {
if (!this.message.mentions) this.message.mentions = [];
this.message.mentions.push({ pos, len: length, uid });
return this;
}
quote(originalMessage: any) {
this.message.quote = originalMessage;
return this;
}
build(): MessageContent {
return { ...this.message };
}
async send(threadId: string, threadType: ThreadType) {
return await api.sendMessage(this.build(), threadId, threadType);
}
}
// Sử dụng
const result = await new MessageBuilder()
.text('Tin nhắn quan trọng với file đính kèm!')
.style(0, 11, TextStyle.Bold)
.style(12, 9, TextStyle.Red)
.important()
.attachment('/path/to/file.pdf')
.ttl(5 * 60 * 1000)
.send('user-id', ThreadType.User);
```
### 2. Template Messages
```typescript
const MessageTemplates = {
welcome: (name: string) => ({
msg: `Xin chào ${name}! 👋 Chào mừng bạn đến với hệ thống.`,
styles: [
{ start: 9, len: name.length, st: TextStyle.Bold }
]
}),
error: (error: string) => ({
msg: `⚠️ Lỗi: ${error}`,
styles: [
{ start: 0, len: 6, st: TextStyle.Red }
],
urgency: Urgency.Important
}),
success: (message: string) => ({
msg: `✅ Thành công: ${message}`,
styles: [
{ start: 0, len: 14, st: TextStyle.Green }
]
}),
reminder: (content: string, minutes: number) => ({
msg: `⏰ Nhắc nhở: ${content}`,
ttl: minutes * 60 * 1000,
urgency: Urgency.Important
})
};
// Sử dụng
await api.sendMessage(MessageTemplates.welcome('John'), 'user-id', ThreadType.User);
await api.sendMessage(MessageTemplates.error('Không thể tải file'), 'user-id', ThreadType.User);
await api.sendMessage(MessageTemplates.reminder('Họp lúc 3h chiều', 60), 'user-id', ThreadType.User);
```
## Best Practices
### 1. Validate Input
```typescript
function validateMessageInput(message: any, threadId: string, threadType: ThreadType) {
if (!message) throw new Error('Message không được để trống');
if (!threadId) throw new Error('ThreadId không được để trống');
if (![ThreadType.User, ThreadType.Group].includes(threadType)) {
throw new Error('ThreadType không hợp lệ');
}
if (typeof message === 'object' && message.msg && message.msg.length > 1000) {
throw new Error('Tin nhắn quá dài (max 1000 ký tự)');
}
}
```
### 2. Async Queue cho Bulk Messages
```typescript
async function sendBulkMessages(messages: Array<{content: any, threadId: string, threadType: ThreadType}>) {
const results = [];
const errors = [];
for (const [index, msg] of messages.entries()) {
try {
console.log(`Gửi tin nhắn ${index + 1}/${messages.length}...`);
const result = await api.sendMessage(msg.content, msg.threadId, msg.threadType);
results.push({ index, result });
// Delay giữa các tin nhắn
if (index < messages.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (error) {
errors.push({ index, error: error.message });
}
}
return { results, errors };
}
```
### 3. Message History
```typescript
class MessageHistory {
private history: Array<{id: string, content: any, threadId: string, timestamp: Date}> = [];
async sendAndRecord(message: any, threadId: string, threadType: ThreadType) {
const result = await api.sendMessage(message, threadId, threadType);
this.history.push({
id: result.message?.msgId?.toString() || 'unknown',
content: message,
threadId,
timestamp: new Date()
});
return result;
}
getHistory() {
return [...this.history];
}
findByThreadId(threadId: string) {
return this.history.filter(msg => msg.threadId === threadId);
}
}
```
## Lưu Ý Quan Trọng
1. **Rate Limiting**: Tránh gửi quá nhiều tin nhắn trong thời gian ngắn
2. **File Size**: Kiểm tra kích thước file trước khi gửi (max ~25MB)
3. **Character Limit**: Tin nhắn văn bản có giới hạn độ dài
4. **Mention**: Chỉ hoạt động trong nhóm
5. **Quote**: Một số loại tin nhắn không thể quote được
6. **TTL**: Không phải tất cả client đều hỗ trợ tin nhắn tự hủy
7. **Attachment**: Kiểm tra loại file được hỗ trợ trước khi gửi
8. **Error Handling**: Luôn wrap API calls trong try-catch