UNPKG

@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
# 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