@warriorteam/redai-zalo-sdk
Version:
Comprehensive TypeScript/JavaScript SDK for Zalo APIs - Official Account, ZNS, Consultation Service, Group Messaging, and Social APIs
489 lines • 20.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConsultationService = void 0;
const common_1 = require("../types/common");
/**
* Service xử lý các API tin nhắn tư vấn của Zalo Official Account
*
* Tin nhắn tư vấn (CS - Customer Service) là loại tin nhắn đặc biệt
* cho phép OA gửi tin nhắn chủ động đến người dùng trong khung thời gian nhất định
*
* ĐIỀU KIỆN GỬI TIN TƯ VẤN:
*
* 1. THỜI GIAN GỬI:
* - Chỉ được gửi trong vòng 48 giờ kể từ khi người dùng tương tác cuối cùng với OA
* - Tương tác bao gồm: gửi tin nhắn, nhấn button, gọi điện, truy cập website từ OA
*
* 2. NỘI DUNG TIN NHẮN:
* - Phải liên quan đến tư vấn, hỗ trợ khách hàng
* - Bao gồm: trả lời câu hỏi, hướng dẫn sử dụng, hỗ trợ kỹ thuật
* - Không được chứa nội dung quảng cáo trực tiếp
*
* 3. TẦN SUẤT GỬI:
* - Không giới hạn số lượng tin nhắn tư vấn trong ngày
* - Tuy nhiên cần tuân thủ nguyên tắc không spam
*
* 4. NGƯỜI DÙNG:
* - Người dùng phải đã follow OA
* - Người dùng không được block OA
* - Người dùng phải có tương tác gần đây với OA
*/
class ConsultationService {
constructor(client) {
this.client = client;
// Zalo API endpoint for consultation messages
this.endpoint = "https://openapi.zalo.me/v3.0/oa/message/cs";
}
/**
* Gửi tin nhắn tư vấn văn bản
* @param accessToken Access token của Official Account
* @param recipient Thông tin người nhận
* @param message Nội dung tin nhắn văn bản
* @returns Thông tin tin nhắn đã gửi
*/
async sendTextMessage(accessToken, recipient, message) {
try {
// Validate text length theo quy định của Zalo
if (!message.text || message.text.trim().length === 0) {
throw new common_1.ZaloSDKError("Nội dung tin nhắn không được để trống", -1);
}
if (message.text.length > 2000) {
throw new common_1.ZaloSDKError("Nội dung tin nhắn không được vượt quá 2000 ký tự", -1);
}
// Request structure theo API spec
const request = {
recipient: {
user_id: recipient.user_id,
},
message: {
text: message.text,
},
};
const result = await this.client.apiPost(this.endpoint, accessToken, request);
if (result.error !== 0) {
throw new common_1.ZaloSDKError(result.message || "Failed to send consultation text message", result.error, result);
}
if (!result.data) {
throw new common_1.ZaloSDKError("No response data received", -1);
}
return result.data;
}
catch (error) {
if (error instanceof common_1.ZaloSDKError) {
throw error;
}
throw new common_1.ZaloSDKError(`Failed to send consultation text message: ${error.message}`, -1, error);
}
}
/**
* Gửi tin nhắn tư vấn trích dẫn (quote message)
* @param accessToken Access token của Official Account
* @param recipient Thông tin người nhận
* @param text Nội dung tin nhắn trả lời (tối đa 2000 ký tự)
* @param quoteMessageId ID của tin nhắn muốn trích dẫn
* @returns Thông tin tin nhắn đã gửi với quota information
*/
async sendQuoteMessage(accessToken, recipient, text, quoteMessageId) {
try {
// Validate input parameters
if (!text || text.trim().length === 0) {
throw new common_1.ZaloSDKError("Nội dung tin nhắn không được để trống", -1);
}
if (text.length > 2000) {
throw new common_1.ZaloSDKError("Nội dung tin nhắn không được vượt quá 2000 ký tự", -1);
}
if (!quoteMessageId || quoteMessageId.trim().length === 0) {
throw new common_1.ZaloSDKError("Quote message ID không được để trống", -1);
}
if (!recipient.user_id || recipient.user_id.trim().length === 0) {
throw new common_1.ZaloSDKError("User ID không được để trống", -1);
}
// Request structure theo API spec
const request = {
recipient: {
user_id: recipient.user_id,
},
message: {
text: text.trim(),
quote_message_id: quoteMessageId.trim(),
},
};
const result = await this.client.apiPost(this.endpoint, accessToken, request);
if (result.error !== 0) {
throw new common_1.ZaloSDKError(result.message || "Failed to send consultation quote message", result.error, result);
}
if (!result.data) {
throw new common_1.ZaloSDKError("No response data received", -1);
}
return result.data;
}
catch (error) {
if (error instanceof common_1.ZaloSDKError) {
throw error;
}
throw new common_1.ZaloSDKError(`Failed to send consultation quote message: ${error.message}`, -1, error);
}
}
/**
* Tạo ConsultationQuoteMessage object
* @param text Nội dung tin nhắn trả lời
* @param quoteMessageId ID của tin nhắn muốn trích dẫn
* @param quoteContent Nội dung của tin nhắn được trích dẫn (optional)
* @returns ConsultationQuoteMessage object
*/
createQuoteMessage(text, quoteMessageId, quoteContent) {
return {
type: "consultation_quote",
text: text.trim(),
quote: {
message_id: quoteMessageId.trim(),
content: quoteContent || "",
},
};
}
/**
* Gửi tin nhắn tư vấn hình ảnh
* Endpoint: https://openapi.zalo.me/v3.0/oa/message/cs
* Method: POST
*
* @param accessToken Access token của Official Account
* @param userId ID người nhận (user_id)
* @param imageUrl URL hình ảnh (jpg, png, tối đa 1MB)
* @param text Tiêu đề ảnh (optional, tối đa 2000 ký tự)
* @returns Thông tin tin nhắn đã gửi
*/
async sendImageMessage(accessToken, userId, imageUrl, text) {
try {
if (!imageUrl || imageUrl.trim().length === 0) {
throw new common_1.ZaloSDKError("URL hình ảnh không được để trống", -1);
}
if (text && text.length > 2000) {
throw new common_1.ZaloSDKError("Tiêu đề ảnh không được vượt quá 2000 ký tự", -1);
}
// Request structure theo API spec
const request = {
recipient: {
user_id: userId,
},
message: {
...(text && { text: text }),
attachment: {
type: "template",
payload: {
template_type: "media",
elements: [
{
media_type: "image",
url: imageUrl,
},
],
},
},
},
};
const result = await this.client.apiPost(this.endpoint, accessToken, request);
if (result.error !== 0) {
throw new common_1.ZaloSDKError(result.message || "Failed to send consultation image message", result.error, result);
}
if (!result.data) {
throw new common_1.ZaloSDKError("No response data received", -1);
}
return result.data;
}
catch (error) {
if (error instanceof common_1.ZaloSDKError) {
throw error;
}
throw new common_1.ZaloSDKError(`Failed to send consultation image message: ${error.message}`, -1, error);
}
}
/**
* Gửi tin nhắn tư vấn hình ảnh bằng attachment_id
* Endpoint: https://openapi.zalo.me/v3.0/oa/message/cs
* Method: POST
*
* @param accessToken Access token của Official Account
* @param userId ID người nhận (user_id)
* @param attachmentId ID của ảnh đã upload (từ API upload ảnh)
* @param text Tiêu đề ảnh (optional, tối đa 2000 ký tự)
* @returns Thông tin tin nhắn đã gửi
*/
async sendImageByAttachmentId(accessToken, userId, attachmentId, text) {
try {
if (!attachmentId || attachmentId.trim().length === 0) {
throw new common_1.ZaloSDKError("Attachment ID không được để trống", -1);
}
if (text && text.length > 2000) {
throw new common_1.ZaloSDKError("Tiêu đề ảnh không được vượt quá 2000 ký tự", -1);
}
// Request structure theo API spec
const request = {
recipient: {
user_id: userId,
},
message: {
...(text && { text: text }),
attachment: {
type: "template",
payload: {
template_type: "media",
elements: [
{
media_type: "image",
attachment_id: attachmentId,
},
],
},
},
},
};
const result = await this.client.apiPost(this.endpoint, accessToken, request);
if (result.error !== 0) {
throw new common_1.ZaloSDKError(result.message || "Failed to send consultation image message", result.error, result);
}
if (!result.data) {
throw new common_1.ZaloSDKError("No response data received", -1);
}
return result.data;
}
catch (error) {
if (error instanceof common_1.ZaloSDKError) {
throw error;
}
throw new common_1.ZaloSDKError(`Failed to send consultation image message: ${error.message}`, -1, error);
}
}
/**
* Gửi tin nhắn tư vấn GIF
* Endpoint: https://openapi.zalo.me/v3.0/oa/message/cs
* Method: POST
*
* @param accessToken Access token của Official Account
* @param userId ID người nhận (user_id)
* @param gifUrl URL ảnh GIF
* @param width Chiều rộng của ảnh (bắt buộc cho GIF)
* @param height Chiều cao của ảnh (bắt buộc cho GIF)
* @param text Tiêu đề ảnh (optional, tối đa 2000 ký tự)
* @returns Thông tin tin nhắn đã gửi
*/
async sendGifMessage(accessToken, userId, gifUrl, width, height, text) {
try {
if (!gifUrl || gifUrl.trim().length === 0) {
throw new common_1.ZaloSDKError("URL GIF không được để trống", -1);
}
if (width <= 0 || height <= 0) {
throw new common_1.ZaloSDKError("Chiều rộng và chiều cao phải lớn hơn 0", -1);
}
if (text && text.length > 2000) {
throw new common_1.ZaloSDKError("Tiêu đề ảnh không được vượt quá 2000 ký tự", -1);
}
// Request structure theo API spec
const request = {
recipient: {
user_id: userId,
},
message: {
...(text && { text: text }),
attachment: {
type: "template",
payload: {
template_type: "media",
elements: [
{
media_type: "gif",
url: gifUrl,
width: width,
height: height,
},
],
},
},
},
};
const result = await this.client.apiPost(this.endpoint, accessToken, request);
if (result.error !== 0) {
throw new common_1.ZaloSDKError(result.message || "Failed to send consultation gif message", result.error, result);
}
if (!result.data) {
throw new common_1.ZaloSDKError("No response data received", -1);
}
return result.data;
}
catch (error) {
if (error instanceof common_1.ZaloSDKError) {
throw error;
}
throw new common_1.ZaloSDKError(`Failed to send consultation gif message: ${error.message}`, -1, error);
}
}
/**
* Gửi tin nhắn tư vấn đính kèm file
* Endpoint: https://openapi.zalo.me/v3.0/oa/message/cs
* Method: POST
*
* @param accessToken Access token của Official Account
* @param userId ID người nhận (user_id)
* @param fileToken Token của file đã upload (từ API upload file)
* @returns Thông tin tin nhắn đã gửi
*
* Note: Cần sử dụng API upload file trước để lấy token
*/
async sendFileMessage(accessToken, userId, fileToken) {
try {
if (!fileToken || fileToken.trim().length === 0) {
throw new common_1.ZaloSDKError("File token không được để trống", -1);
}
// Request structure theo API spec
const request = {
recipient: {
user_id: userId,
},
message: {
attachment: {
type: "file",
payload: {
token: fileToken,
},
},
},
};
const result = await this.client.apiPost(this.endpoint, accessToken, request);
if (result.error !== 0) {
throw new common_1.ZaloSDKError(result.message || "Failed to send consultation file message", result.error, result);
}
if (!result.data) {
throw new common_1.ZaloSDKError("No response data received", -1);
}
return result.data;
}
catch (error) {
if (error instanceof common_1.ZaloSDKError) {
throw error;
}
throw new common_1.ZaloSDKError(`Failed to send consultation file message: ${error.message}`, -1, error);
}
}
/**
* Gửi tin nhắn tư vấn theo mẫu yêu cầu thông tin người dùng
* Endpoint: https://openapi.zalo.me/v3.0/oa/message/cs
* Method: POST
*
* @param accessToken Access token của Official Account
* @param userId ID người nhận (user_id)
* @param title Tiêu đề hiển thị của template (tối đa 100 ký tự)
* @param subtitle Tiêu đề phụ của template (tối đa 500 ký tự)
* @param imageUrl Đường dẫn đến ảnh
* @returns Thông tin tin nhắn đã gửi
*/
async sendRequestUserInfoMessage(accessToken, userId, title, subtitle, imageUrl) {
try {
// Validation theo API spec
if (!title || title.trim().length === 0) {
throw new common_1.ZaloSDKError("Tiêu đề không được để trống", -1);
}
if (title.length > 100) {
throw new common_1.ZaloSDKError("Tiêu đề không được vượt quá 100 ký tự", -1);
}
if (!subtitle || subtitle.trim().length === 0) {
throw new common_1.ZaloSDKError("Tiêu đề phụ không được để trống", -1);
}
if (subtitle.length > 500) {
throw new common_1.ZaloSDKError("Tiêu đề phụ không được vượt quá 500 ký tự", -1);
}
if (!imageUrl || imageUrl.trim().length === 0) {
throw new common_1.ZaloSDKError("URL hình ảnh không được để trống", -1);
}
// Request structure theo API spec
const request = {
recipient: {
user_id: userId,
},
message: {
attachment: {
type: "template",
payload: {
template_type: "request_user_info",
elements: [
{
title: title,
subtitle: subtitle,
image_url: imageUrl,
},
],
},
},
},
};
const result = await this.client.apiPost(this.endpoint, accessToken, request);
if (result.error !== 0) {
throw new common_1.ZaloSDKError(result.message || "Failed to send request user info message", result.error, result);
}
if (!result.data) {
throw new common_1.ZaloSDKError("No response data received", -1);
}
return result.data;
}
catch (error) {
if (error instanceof common_1.ZaloSDKError) {
throw error;
}
throw new common_1.ZaloSDKError(`Failed to send request user info message: ${error.message}`, -1, error);
}
}
/**
* Gửi tin nhắn tư vấn kèm Sticker
* Endpoint: https://openapi.zalo.me/v3.0/oa/message/cs
* Method: POST
*
* @param accessToken Access token của Official Account
* @param userId ID người nhận (user_id)
* @param stickerAttachmentId ID của sticker (lấy từ https://stickers.zaloapp.com/)
* @returns Thông tin tin nhắn đã gửi
*
* Note: Sticker ID lấy từ nguồn https://stickers.zaloapp.com/
* Xem video hướng dẫn: https://vimeo.com/649330161
*/
async sendStickerMessage(accessToken, userId, stickerAttachmentId) {
try {
if (!stickerAttachmentId || stickerAttachmentId.trim().length === 0) {
throw new common_1.ZaloSDKError("Sticker attachment ID không được để trống", -1);
}
// Request structure theo API spec
const request = {
recipient: {
user_id: userId,
},
message: {
attachment: {
type: "template",
payload: {
template_type: "media",
elements: [
{
media_type: "sticker",
attachment_id: stickerAttachmentId,
},
],
},
},
},
};
const result = await this.client.apiPost(this.endpoint, accessToken, request);
if (result.error !== 0) {
throw new common_1.ZaloSDKError(result.message || "Failed to send consultation sticker message", result.error, result);
}
if (!result.data) {
throw new common_1.ZaloSDKError("No response data received", -1);
}
return result.data;
}
catch (error) {
if (error instanceof common_1.ZaloSDKError) {
throw error;
}
throw new common_1.ZaloSDKError(`Failed to send consultation sticker message: ${error.message}`, -1, error);
}
}
}
exports.ConsultationService = ConsultationService;
//# sourceMappingURL=consultation.service.js.map