@warriorteam/redai-zalo-sdk
Version:
Comprehensive TypeScript/JavaScript SDK for Zalo APIs - Official Account, ZNS, Consultation Service, Group Messaging, and Social APIs
722 lines • 30.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GroupManagementService = void 0;
const common_1 = require("../types/common");
/**
* Service for handling Zalo Official Account Group Management Framework (GMF) APIs
*
* CONDITIONS FOR USING ZALO GMF GROUP MANAGEMENT:
*
* 1. GENERAL CONDITIONS:
* - OA must be granted permission to use GMF (Group Message Framework) feature
* - Access token must have "manage_group" and "group_message" scopes
* - OA must have active status and be verified
* - Must comply with limits on number of groups and members
*
* 2. CREATE NEW GROUP:
* - Group name: required, max 100 characters, no special characters
* - Description: optional, max 500 characters
* - Avatar: optional, JPG/PNG format, max 5MB
* - Initial members: max 200 people, must be users who have interacted with OA
* - OA automatically becomes admin of the group
*
* 3. MEMBER MANAGEMENT:
* - Only admins can invite/remove members
* - Invite members: max 50 people per time, users must have interacted with OA
* - Remove members: cannot remove other admins, must have at least 1 admin
* - Members can leave group themselves
*
* 4. ADMIN MANAGEMENT:
* - Only current admins can add/remove other admins
* - Must have at least 1 admin in group
* - OA always has admin rights and cannot be removed
*
* 5. LIMITS AND CONSTRAINTS:
* - Maximum groups: according to service package (usually 10-100 groups)
* - Maximum members per group: 200 people
* - Group creation frequency: max 10 groups/day
* - Member invitation frequency: max 500 invitations/day
*/
class GroupManagementService {
constructor(client) {
this.client = client;
// Zalo API endpoints - organized by functionality
this.endpoints = {
group: {
create: "https://openapi.zalo.me/v3.0/oa/group/creategroupwithoa",
get: "https://openapi.zalo.me/v3.0/oa/group/getgroup",
updateInfo: "https://openapi.zalo.me/v3.0/oa/group/updateinfo",
updateAsset: "https://openapi.zalo.me/v3.0/oa/group/updateasset",
invite: "https://openapi.zalo.me/v3.0/oa/group/invite",
listPendingInvite: "https://openapi.zalo.me/v3.0/oa/group/listpendinginvite",
acceptPendingInvite: "https://openapi.zalo.me/v3.0/oa/group/acceptpendinginvite",
rejectPendingInvite: "https://openapi.zalo.me/v3.0/oa/group/rejectpendinginvite",
removeMembers: "https://openapi.zalo.me/v3.0/oa/group/removemembers",
getGroupsOfOA: "https://openapi.zalo.me/v3.0/oa/group/getgroupsofoa",
recent: "https://openapi.zalo.me/v3.0/oa/group/listrecentchat",
conversation: "https://openapi.zalo.me/v3.0/oa/group/conversation",
addAdmins: "https://openapi.zalo.me/v3.0/oa/group/addadmins",
removeAdmins: "https://openapi.zalo.me/v3.0/oa/group/removeadmins",
conversationByGroupId: (groupId) => `https://openapi.zalo.me/v3.0/oa/group/${groupId}/conversation`,
members: "https://openapi.zalo.me/v3.0/oa/group/listmember",
delete: "https://openapi.zalo.me/v3.0/oa/group/delete",
quota: "https://openapi.zalo.me/v3.0/oa/quota/group",
},
};
}
/**
* Create new group chat with asset_id
* @param accessToken OA access token
* @param groupData Group information to create
* @returns Created group information
*
* API: POST https://openapi.zalo.me/v3.0/oa/group/creategroupwithoa
*/
async createGroup(accessToken, groupData) {
try {
// Validate input
if (!groupData.group_name || groupData.group_name.trim().length === 0) {
throw new common_1.ZaloSDKError("Group name cannot be empty", -1);
}
if (groupData.group_name.length > 100) {
throw new common_1.ZaloSDKError("Group name cannot exceed 100 characters", -1);
}
if (!groupData.asset_id || groupData.asset_id.trim().length === 0) {
throw new common_1.ZaloSDKError("Asset ID cannot be empty", -1);
}
if (groupData.member_user_ids.length > 99) {
throw new common_1.ZaloSDKError("Initial member count cannot exceed 99 people", -1);
}
if (groupData.member_user_ids.length === 0) {
throw new common_1.ZaloSDKError("Member list cannot be empty", -1);
}
const requestData = {
group_name: groupData.group_name.trim(),
...(groupData.group_description && {
group_description: groupData.group_description.trim(),
}),
asset_id: groupData.asset_id,
member_user_ids: groupData.member_user_ids,
};
const response = await this.client.apiPost(this.endpoints.group.create, accessToken, requestData);
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to create group");
}
}
/**
* Helper method to extract group data from create response
* @param response Full API response
* @returns Group data only
*/
extractGroupData(response) {
return response.data;
}
/**
* Helper method to extract group info from update response
* @param response Full API response
* @returns Group info only
*/
extractGroupInfo(response) {
return response.data.group_info;
}
/**
* Helper method to extract group settings from update response
* @param response Full API response
* @returns Group settings only
*/
extractGroupSettings(response) {
return response.data.group_setting;
}
/**
* Helper method to extract asset info from update response
* @param response Full API response
* @returns Asset info only
*/
extractAssetInfo(response) {
return response.data.asset_info;
}
async updateGroupAsset(accessToken, groupIdOrUpdateData, assetId) {
try {
let requestData;
// Handle overloaded parameters
if (typeof groupIdOrUpdateData === 'string') {
// First overload: (accessToken, groupId, assetId)
if (!assetId) {
throw new common_1.ZaloSDKError("Asset ID is required when using separate parameters", -1);
}
requestData = {
group_id: groupIdOrUpdateData,
asset_id: assetId,
};
}
else {
// Second overload: (accessToken, updateData)
requestData = groupIdOrUpdateData;
}
// Validate input
if (!requestData.group_id || requestData.group_id.trim().length === 0) {
throw new common_1.ZaloSDKError("Group ID cannot be empty", -1);
}
if (!requestData.asset_id || requestData.asset_id.trim().length === 0) {
throw new common_1.ZaloSDKError("Asset ID cannot be empty", -1);
}
const response = await this.client.apiPost(this.endpoints.group.updateAsset, accessToken, requestData);
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to update group asset");
}
}
/**
* Get detailed group information
* @param accessToken OA access token
* @param groupId Group ID
* @returns Detailed group information including group_info, asset_info and group_setting
*
* API: GET https://openapi.zalo.me/v3.0/oa/group/getgroup
*/
async getGroupInfo(accessToken, groupId) {
try {
// Validate access token
if (!accessToken || accessToken.trim().length === 0) {
throw new common_1.ZaloSDKError("Access token cannot be empty", -1);
}
// Validate group ID
if (!groupId || groupId.trim().length === 0) {
throw new common_1.ZaloSDKError("Group ID cannot be empty", -1);
}
const response = await this.client.apiGet(this.endpoints.group.get, accessToken, {
group_id: groupId.trim(),
});
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to get group information");
}
}
/**
* Update group information
* @param accessToken OA access token
* @param groupId Group ID
* @param updateData Information to update
* @returns Update result with full group information
*
* API: POST https://openapi.zalo.me/v3.0/oa/group/updateinfo
*/
async updateGroupInfo(accessToken, groupId, updateData) {
try {
// Validate input
if (updateData.group_name && updateData.group_name.length > 100) {
throw new common_1.ZaloSDKError("Group name cannot exceed 100 characters", -1);
}
if (updateData.group_description && updateData.group_description.length > 500) {
throw new common_1.ZaloSDKError("Group description cannot exceed 500 characters", -1);
}
// Build request data according to Zalo API
const requestData = {
group_id: groupId,
...(updateData.group_name && {
group_name: updateData.group_name.trim(),
}),
...(updateData.group_avatar && {
group_avatar: updateData.group_avatar,
}),
...(updateData.group_description && {
group_description: updateData.group_description.trim(),
}),
...(updateData.lock_send_msg !== undefined && {
lock_send_msg: updateData.lock_send_msg,
}),
...(updateData.join_appr !== undefined && {
join_appr: updateData.join_appr,
}),
...(updateData.enable_msg_history !== undefined && {
enable_msg_history: updateData.enable_msg_history,
}),
...(updateData.enable_link_join !== undefined && {
enable_link_join: updateData.enable_link_join,
}),
};
const response = await this.client.apiPost(this.endpoints.group.updateInfo, accessToken, requestData);
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to update group information");
}
}
/**
* Update group avatar
* @param accessToken OA access token
* @param groupId Group ID
* @param avatarData New avatar information
* @returns Update result
* @deprecated Use updateGroupInfo() with group_avatar field instead
*/
async updateGroupAvatar(accessToken, groupId, avatarData) {
try {
const response = await this.client.apiPost(this.endpoints.group.updateInfo, accessToken, {
group_id: groupId,
...avatarData,
});
return { success: response.error === 0 };
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to update group avatar");
}
}
async inviteMembers(accessToken, groupId, inviteDataOrUserIds) {
try {
let memberUserIds;
// Handle overloaded parameters
if (Array.isArray(inviteDataOrUserIds)) {
// Second overload: (accessToken, groupId, memberUserIds)
memberUserIds = inviteDataOrUserIds;
}
else {
// First overload: (accessToken, groupId, inviteData)
memberUserIds = inviteDataOrUserIds.member_user_ids;
}
// Validate input
if (memberUserIds.length === 0) {
throw new common_1.ZaloSDKError("Member list cannot be empty", -1);
}
if (memberUserIds.length > 50) {
throw new common_1.ZaloSDKError("Cannot invite more than 50 people at once", -1);
}
const requestData = {
group_id: groupId,
member_user_ids: memberUserIds,
};
const response = await this.client.apiPost(this.endpoints.group.invite, accessToken, requestData);
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to invite members");
}
}
/**
* Get list of pending members
* @param accessToken OA access token
* @param groupId Group ID
* @param offset Offset for pagination (default: 0)
* @param count Maximum number to return (default: 20, max: 50)
* @returns List of pending members
*/
async getPendingMembers(accessToken, groupId, offset = 0, count = 5) {
try {
// Validate input
if (!groupId || groupId.trim().length === 0) {
throw new common_1.ZaloSDKError("Group ID cannot be empty", -1);
}
if (offset < 0) {
throw new common_1.ZaloSDKError("Offset must be >= 0", -1);
}
if (count <= 0 || count > 50) {
throw new common_1.ZaloSDKError("Count must be from 1 to 50", -1);
}
const response = await this.client.apiGet(this.endpoints.group.listPendingInvite, accessToken, {
group_id: groupId.trim(),
offset: offset,
count: count,
});
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to get pending members");
}
}
/**
* Accept pending members to group
* @param accessToken OA access token
* @param groupId Group ID
* @param memberUserIds List of user IDs to accept
* @returns Accept result
*/
async acceptPendingMembers(accessToken, groupId, memberUserIds) {
try {
// Validate input
if (!groupId || groupId.trim().length === 0) {
throw new common_1.ZaloSDKError("Group ID cannot be empty", -1);
}
if (!memberUserIds || memberUserIds.length === 0) {
throw new common_1.ZaloSDKError("Member user IDs list cannot be empty", -1);
}
if (memberUserIds.length > 100) {
throw new common_1.ZaloSDKError("Cannot accept more than 100 members at once", -1);
}
// Validate user IDs
const validUserIds = memberUserIds.filter((id) => id && id.trim().length > 0);
if (validUserIds.length === 0) {
throw new common_1.ZaloSDKError("No valid user IDs found", -1);
}
const requestBody = {
group_id: groupId.trim(),
member_user_ids: validUserIds.map((id) => id.trim()),
};
const response = await this.client.apiPost(this.endpoints.group.acceptPendingInvite, accessToken, requestBody);
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to accept pending members");
}
}
/**
* Reject pending members from group
* @param accessToken OA access token
* @param groupId Group ID
* @param memberUserIds List of user IDs to reject
* @returns Reject result
*/
async rejectPendingMembers(accessToken, groupId, memberUserIds) {
try {
// Validate input
if (!groupId || groupId.trim().length === 0) {
throw new common_1.ZaloSDKError("Group ID cannot be empty", -1);
}
if (!memberUserIds || memberUserIds.length === 0) {
throw new common_1.ZaloSDKError("Member user IDs list cannot be empty", -1);
}
if (memberUserIds.length > 100) {
throw new common_1.ZaloSDKError("Cannot reject more than 100 members at once", -1);
}
// Validate user IDs
const validUserIds = memberUserIds.filter((id) => id && id.trim().length > 0);
if (validUserIds.length === 0) {
throw new common_1.ZaloSDKError("No valid user IDs found", -1);
}
const requestBody = {
group_id: groupId.trim(),
member_user_ids: validUserIds.map((id) => id.trim()),
};
const response = await this.client.apiPost(this.endpoints.group.rejectPendingInvite, accessToken, requestBody);
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to reject pending members");
}
}
/**
* Remove members from group
* @param accessToken OA access token
* @param groupId Group ID
* @param memberUserIds List of user IDs to remove
* @returns Remove result
*/
async removeMembers(accessToken, groupId, memberUserIds) {
try {
// Validate input
if (!groupId || groupId.trim().length === 0) {
throw new common_1.ZaloSDKError("Group ID cannot be empty", -1);
}
if (!memberUserIds || memberUserIds.length === 0) {
throw new common_1.ZaloSDKError("Member user IDs list cannot be empty", -1);
}
if (memberUserIds.length > 100) {
throw new common_1.ZaloSDKError("Cannot remove more than 100 members at once", -1);
}
// Validate user IDs
const validUserIds = memberUserIds.filter((id) => id && id.trim().length > 0);
if (validUserIds.length === 0) {
throw new common_1.ZaloSDKError("No valid user IDs found", -1);
}
const requestBody = {
group_id: groupId.trim(),
member_user_ids: validUserIds.map((id) => id.trim()),
};
const response = await this.client.apiPost(this.endpoints.group.removeMembers, accessToken, requestBody);
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to remove members");
}
}
async addAdmins(accessToken, groupId, adminDataOrUserIds) {
try {
let memberUserIds;
// Handle overloaded parameters
if (Array.isArray(adminDataOrUserIds)) {
// Second overload: (accessToken, groupId, memberUserIds)
memberUserIds = adminDataOrUserIds;
}
else {
// First overload: (accessToken, groupId, adminData)
memberUserIds = adminDataOrUserIds.member_user_ids;
}
// Validate input
if (!memberUserIds || memberUserIds.length === 0) {
throw new common_1.ZaloSDKError("Member user IDs list cannot be empty", -1);
}
const requestData = {
group_id: groupId,
member_user_ids: memberUserIds,
};
const response = await this.client.apiPost(this.endpoints.group.addAdmins, accessToken, requestData);
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to add admins");
}
}
async removeAdmins(accessToken, groupId, adminDataOrUserIds) {
try {
let memberUserIds;
// Handle overloaded parameters
if (Array.isArray(adminDataOrUserIds)) {
// Second overload: (accessToken, groupId, memberUserIds)
memberUserIds = adminDataOrUserIds;
}
else {
// First overload: (accessToken, groupId, adminData)
memberUserIds = adminDataOrUserIds.member_user_ids;
}
// Validate input
if (!memberUserIds || memberUserIds.length === 0) {
throw new common_1.ZaloSDKError("Member user IDs list cannot be empty", -1);
}
const requestData = {
group_id: groupId,
member_user_ids: memberUserIds,
};
const response = await this.client.apiPost(this.endpoints.group.removeAdmins, accessToken, requestData);
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to remove admins");
}
}
/**
* Delete group chat (Disband group)
* @param accessToken OA access token
* @param groupId Group ID to delete
* @returns Delete result
*/
async deleteGroup(accessToken, groupId) {
try {
const requestData = {
group_id: groupId,
};
const response = await this.client.apiPost(this.endpoints.group.delete, accessToken, requestData);
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to delete group");
}
}
/**
* Get list of OA groups
* @param accessToken OA access token
* @param offset Offset for pagination (default: 0)
* @param count Maximum number to return (default: 5, max: 50)
* @returns List of OA groups
*
* API: GET https://openapi.zalo.me/v3.0/oa/group/getgroupsofoa
*/
async getGroupsOfOA(accessToken, offset = 0, count = 5) {
try {
// Validate access token
if (!accessToken || accessToken.trim().length === 0) {
throw new common_1.ZaloSDKError("Access token cannot be empty", -1);
}
// Validate parameters
if (offset < 0) {
throw new common_1.ZaloSDKError("Offset must be >= 0", -1);
}
if (count <= 0 || count > 50) {
throw new common_1.ZaloSDKError("Count must be from 1 to 50", -1);
}
const response = await this.client.apiGet(this.endpoints.group.getGroupsOfOA, accessToken, {
offset: offset,
count: count,
});
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to get groups of OA");
}
}
/**
* Get group quota information and asset_id
* @param accessToken OA access token
* @param productType Product type (optional)
* @param quotaType Quota type (optional)
* @returns Group quota information including asset_id
*
* API: POST https://openapi.zalo.me/v3.0/oa/quota/group
*/
async getGroupQuota(accessToken, productType, quotaType) {
try {
// Validate access token
if (!accessToken || accessToken.trim().length === 0) {
throw new common_1.ZaloSDKError("Access token cannot be empty", -1);
}
const quotaRequest = {
quota_owner: "OA",
...(productType && { product_type: productType }),
...(quotaType && { quota_type: quotaType }),
};
const response = await this.client.apiPost(this.endpoints.group.quota, accessToken, quotaRequest);
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to get group quota");
}
}
/**
* Get asset_id for creating GMF group
* @param accessToken OA access token
* @returns Asset_id for group creation
*/
async getAssetId(accessToken) {
try {
const quotaRequest = {
quota_owner: "OA",
};
const response = await this.client.apiPost(this.endpoints.group.quota, accessToken, quotaRequest);
if (response.error !== 0) {
throw new common_1.ZaloSDKError(response.message || "Failed to get asset_id", response.error);
}
if (response.data && response.data.length > 0) {
const activeAsset = response.data.find((asset) => asset.status === "available");
if (activeAsset) {
return activeAsset.asset_id;
}
}
throw new common_1.ZaloSDKError("No valid asset_id found for group creation. Please check your GMF package.", -1);
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to get asset_id for group creation");
}
}
/**
* Get list of asset_ids available for creating GMF groups
* @param accessToken OA access token
* @returns List of asset_ids and quota information
*/
async getAssetIds(accessToken) {
try {
const quotaRequest = {
quota_owner: "OA",
};
const response = await this.client.apiPost(this.endpoints.group.quota, accessToken, quotaRequest);
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to get asset_ids list");
}
}
/**
* Get list of recent chats
* @param accessToken OA access token
* @param offset Offset for pagination (default: 0)
* @param count Maximum number to return (default: 5, max: 50)
* @returns List of recent chats
*
* API: GET https://openapi.zalo.me/v3.0/oa/group/listrecentchat
*/
async getRecentChats(accessToken, offset = 0, count = 5) {
try {
// Validate access token
if (!accessToken || accessToken.trim().length === 0) {
throw new common_1.ZaloSDKError("Access token cannot be empty", -1);
}
// Validate parameters
if (offset < 0) {
throw new common_1.ZaloSDKError("Offset must be >= 0", -1);
}
if (count <= 0 || count > 50) {
throw new common_1.ZaloSDKError("Count must be from 1 to 50", -1);
}
const response = await this.client.apiGet(this.endpoints.group.recent, accessToken, {
offset: offset,
count: count,
});
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to get recent chats");
}
}
/**
* Get group conversation history
* @param accessToken OA access token
* @param groupId Group ID
* @param offset Offset for pagination (default: 0)
* @param count Maximum number of messages to return (default: 5, max: 100)
* @returns Group conversation history
*
* API: GET https://openapi.zalo.me/v3.0/oa/group/conversation
*/
async getGroupConversation(accessToken, groupId, offset = 0, count = 5) {
try {
// Validate access token
if (!accessToken || accessToken.trim().length === 0) {
throw new common_1.ZaloSDKError("Access token cannot be empty", -1);
}
// Validate group ID
if (!groupId || groupId.trim().length === 0) {
throw new common_1.ZaloSDKError("Group ID cannot be empty", -1);
}
// Validate parameters
if (offset < 0) {
throw new common_1.ZaloSDKError("Offset must be >= 0", -1);
}
if (count <= 0 || count > 100) {
throw new common_1.ZaloSDKError("Count must be from 1 to 100", -1);
}
const response = await this.client.apiGet(this.endpoints.group.conversation, accessToken, {
group_id: groupId.trim(),
offset: offset,
count: count,
});
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to get group conversation");
}
}
/**
* Get group members list from Zalo API
* @param accessToken OA access token
* @param groupId Group ID
* @param offset Offset for pagination (default: 0)
* @param count Maximum number to return (default: 5, max: 50)
* @returns Group members list
*/
async getGroupMembers(accessToken, groupId, offset = 0, count = 5) {
try {
// Validate input
if (!groupId || groupId.trim().length === 0) {
throw new common_1.ZaloSDKError("Group ID cannot be empty", -1);
}
if (offset < 0) {
throw new common_1.ZaloSDKError("Offset must be >= 0", -1);
}
if (count <= 0 || count > 50) {
throw new common_1.ZaloSDKError("Count must be from 1 to 50", -1);
}
const response = await this.client.apiGet(this.endpoints.group.members, accessToken, {
group_id: groupId.trim(),
offset: offset,
count: count,
});
return response;
}
catch (error) {
throw this.handleGroupManagementError(error, "Failed to get group members");
}
}
handleGroupManagementError(error, defaultMessage) {
if (error instanceof common_1.ZaloSDKError) {
return error;
}
if (error.response?.data) {
const errorData = error.response.data;
return new common_1.ZaloSDKError(`${defaultMessage}: ${errorData.message || errorData.error || "Unknown error"}`, errorData.error || -1, errorData);
}
return new common_1.ZaloSDKError(`${defaultMessage}: ${error.message || "Unknown error"}`, -1, error);
}
}
exports.GroupManagementService = GroupManagementService;
//# sourceMappingURL=group-management.service.js.map