UNPKG

@warriorteam/redai-zalo-sdk

Version:

Comprehensive TypeScript/JavaScript SDK for Zalo APIs - Official Account v3.0, ZNS with Full Type Safety, Consultation Service, Broadcast Service, Group Messaging with List APIs, Social APIs, Enhanced Article Management, Promotion Service v3.0 with Multip

854 lines 36.6 kB
"use strict"; /** * User management service for Zalo API * Handles all user-related operations with specific endpoints */ Object.defineProperty(exports, "__esModule", { value: true }); exports.UserService = void 0; const common_1 = require("../types/common"); /** * User management service for handling user operations * Each method uses specific Zalo API endpoints */ class UserService { constructor(client) { this.client = client; // Zalo API endpoints - organized by functionality this.endpoints = { // User management endpoints user: { getList: "https://openapi.zalo.me/v3.0/oa/user/getlist", postList: "https://openapi.zalo.me/v3.0/oa/user/getlist", getDetail: "https://openapi.zalo.me/v3.0/oa/user/detail", update: "https://openapi.zalo.me/v3.0/oa/user/update", delete: "https://openapi.zalo.me/v2.0/oa/deletefollowerinfo", getCustomInfo: "https://openapi.zalo.me/v3.0/oa/user/detail/custominfo", updateCustomInfo: "https://openapi.zalo.me/v3.0/oa/user/update/custominfo", }, // Tag management endpoints tag: { addToUser: "https://openapi.zalo.me/v2.0/oa/tag/tagfollower", removeFromUser: "https://openapi.zalo.me/v2.0/oa/tag/rmfollowerfromtag", getList: "https://openapi.zalo.me/v2.0/oa/tag/gettagsofoa", delete: "https://openapi.zalo.me/v2.0/oa/tag/rmtag", }, }; } /** * Truy xuất chi tiết người dùng * Endpoint: https://openapi.zalo.me/v3.0/oa/user/detail * Method: GET with data parameter as JSON string * * @param accessToken OA access token * @param userId User ID to get details for * @returns Promise<UserInfo> - Detailed user information */ async getUserInfo(accessToken, userId) { try { // Build data object according to API spec const dataObject = { user_id: userId }; // Send data as JSON string parameter according to API spec const params = { data: JSON.stringify(dataObject), }; const result = await this.client.apiGet(this.endpoints.user.getDetail, accessToken, params); if (result.error !== 0) { throw new common_1.ZaloSDKError(result.message || "Failed to get user information", result.error, result); } if (!result.data) { throw new common_1.ZaloSDKError("No user data received", -1); } return result.data; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } this.handleError(error, "Failed to get user info"); } } /** * Truy xuất chi tiết người dùng (bản dùng POST) * Endpoint: https://openapi.zalo.me/v3.0/oa/user/detail * Method: POST với body JSON * * @param accessToken OA access token * @param userId ID người dùng cần lấy thông tin * @returns Promise<UserInfo> - Thông tin chi tiết người dùng */ async postUserInfo(accessToken, userId) { try { // Tạo body JSON theo yêu cầu API const body = { user_id: userId, }; // Gửi POST request thay vì GET const result = await this.client.apiPost(this.endpoints.user.getDetail, accessToken, body); // Kiểm tra lỗi từ API Zalo if (result.error !== 0) { throw new common_1.ZaloSDKError(result.message || "Failed to get user information (POST)", result.error, result); } // Kiểm tra dữ liệu trả về if (!result.data) { throw new common_1.ZaloSDKError("No user data received from POST request", -1); } return result.data; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } this.handleError(error, "Failed to get user info (POST)"); } } /** * Truy xuất danh sách người dùng * Endpoint: https://openapi.zalo.me/v3.0/oa/user/getlist * Method: GET with data parameter as JSON string * * @param accessToken OA access token * @param request User list request parameters * @returns Promise<UserListResponse> - List of users with pagination info */ async getUserList(accessToken, request) { try { // Build data object according to API spec const dataObject = { offset: request.offset, count: Math.min(request.count, 50), // Max 50 users per request ...(request.tag_name && { tag_name: request.tag_name }), ...(request.last_interaction_period && { last_interaction_period: request.last_interaction_period, }), ...(request.is_follower !== undefined && { is_follower: request.is_follower, }), }; // Send data as JSON string parameter according to API spec const params = { data: JSON.stringify(dataObject), }; const result = await this.client.apiGet(this.endpoints.user.getList, accessToken, params); if (result.error !== 0) { throw new common_1.ZaloSDKError(result.message || "Failed to get user list", result.error, result); } if (!result.data) { throw new common_1.ZaloSDKError("No user list data received", -1); } return result.data; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } this.handleError(error, "Failed to get user list"); } } /** * Truy xuất danh sách người dùng (POST method) * Endpoint: https://openapi.zalo.me/v3.0/oa/user/getlist * Method: POST with data in request body * * @param accessToken OA access token * @param request User list request parameters * @returns Promise<UserListResponse> - List of users with pagination info */ async postUserList(accessToken, request) { try { // Build data object according to API spec const dataObject = { offset: request.offset, count: Math.min(request.count, 50), // Max 50 users per request ...(request.tag_name && { tag_name: request.tag_name }), ...(request.last_interaction_period && { last_interaction_period: request.last_interaction_period, }), ...(request.is_follower !== undefined && { is_follower: request.is_follower, }), }; const result = await this.client.apiPost(this.endpoints.user.postList, accessToken, dataObject); if (result.error !== 0) { throw new common_1.ZaloSDKError(result.message || "Failed to get user list", result.error, result); } if (!result.data) { throw new common_1.ZaloSDKError("No user list data received", -1); } return result.data; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } this.handleError(error, "Failed to get user list"); } } /** * Get all users with automatic pagination * Lấy tất cả users với phân trang tự động, đảm bảo lấy 100% * * @param accessToken Access token của OA * @param filters Bộ lọc tùy chọn * @returns Promise<UserInfo[]> - Danh sách đầy đủ tất cả users */ async getAllUsers(accessToken, filters) { console.log('[getAllUsers] Bắt đầu lấy danh sách users với filters:', filters); const users = []; let offset = 0; const count = 50; // Max per request let totalUsers = 0; let fetchedUsers = 0; let failedUsers = 0; try { while (true) { console.log(`[getAllUsers] Đang lấy batch tại offset=${offset}, count=${count}`); const userListResponse = await this.postUserList(accessToken, { offset, count, ...filters, }); console.log(`[getAllUsers] Nhận được ${userListResponse.users.length} user IDs từ postUserList`); // Lần đầu tiên, lưu tổng số users if (offset === 0) { totalUsers = userListResponse.total; console.log(`[getAllUsers] ✓ Total users to fetch: ${totalUsers}`); // Cảnh báo nếu vượt quá giới hạn Zalo API if (totalUsers > 10000) { console.warn(`[getAllUsers] ⚠ Warning: Total users (${totalUsers}) exceeds Zalo API limit (10000). ` + `Only first 10000 users can be fetched due to max offset = 9951.`); } } // Get detailed info for each user (sequential to avoid rate limit) // Xử lý tuần tự để tránh bị Zalo API rate limit const userIds = userListResponse.users.map((u) => u.user_id); console.log(`[getAllUsers] Bắt đầu lấy chi tiết cho ${userIds.length} users...`); const userInfos = []; for (let i = 0; i < userIds.length; i++) { const userId = userIds[i]; console.log(`[getAllUsers] ├─ [${i + 1}/${userIds.length}] Đang lấy info cho user: ${userId}`); try { const userInfo = await this.postUserInfo(accessToken, userId); userInfos.push(userInfo); console.log(`[getAllUsers] │ ✓ Thành công: ${userInfo.display_name || 'N/A'}`); } catch (error) { failedUsers++; console.warn(`[getAllUsers] │ ⚠ Skip user ${userId} do lỗi: ${error.message}`); // Tiếp tục với user tiếp theo thay vì throw error } // Small delay between requests to avoid rate limiting if (i < userIds.length - 1) { await new Promise((resolve) => setTimeout(resolve, 100)); } } console.log(`[getAllUsers] ✓ Hoàn thành lấy chi tiết ${userInfos.length} users trong batch này`); users.push(...userInfos); fetchedUsers += userInfos.length; console.log(`[getAllUsers] 📊 Progress: Đã lấy ${fetchedUsers}/${totalUsers} users (${Math.round((fetchedUsers / totalUsers) * 100)}%)`); // Điều kiện dừng ĐÚNG: // 1. Không còn users trong response // 2. Hoặc đã lấy đủ số lượng theo total // 3. Hoặc số users trả về ít hơn count (page cuối) if (userListResponse.users.length === 0 || fetchedUsers >= totalUsers || userListResponse.users.length < count) { console.log(`[getAllUsers] 🛑 Điều kiện dừng:`, { noMoreUsers: userListResponse.users.length === 0, reachedTotal: fetchedUsers >= totalUsers, lastPage: userListResponse.users.length < count }); break; } offset += count; console.log(`[getAllUsers] ➡ Chuyển sang batch tiếp theo, offset=${offset}`); // Protection: Tránh infinite loop // Zalo giới hạn max offset = 9951 (tương ứng 10000 người dùng) if (offset > 9951) { console.warn(`[getAllUsers] ⚠ Reached Zalo maximum offset limit (9951), stopping pagination`); break; } } console.log(`[getAllUsers] ✅ HOÀN THÀNH: Successfully fetched ${users.length}/${totalUsers} users` + (failedUsers > 0 ? ` (${failedUsers} failed/skipped)` : '')); return users; } catch (error) { console.error('[getAllUsers] ❌ LỖI:', { message: error.message, code: error.code || error.error, offset: offset, fetchedSoFar: fetchedUsers, totalTarget: totalUsers }); if (error instanceof common_1.ZaloSDKError) { throw error; } this.handleError(error, "Failed to get all users"); } } /** * Get all user IDs only (without detailed info) - Faster version * Chỉ lấy danh sách user ID, không lấy thông tin chi tiết - Nhanh hơn * * @param accessToken Access token của OA * @param filters Bộ lọc tùy chọn * @returns Promise<string[]> - Danh sách user IDs */ async getAllUserIds(accessToken, filters) { const userIds = []; let offset = 0; const count = 50; // Max per request let totalUsers = 0; try { while (true) { const userListResponse = await this.postUserList(accessToken, { offset, count, ...filters, }); // Lần đầu tiên, lưu tổng số users if (offset === 0) { totalUsers = userListResponse.total; console.log(`Total user IDs to fetch: ${totalUsers}`); // Cảnh báo nếu vượt quá giới hạn Zalo API if (totalUsers > 10000) { console.warn(`Warning: Total users (${totalUsers}) exceeds Zalo API limit (10000). ` + `Only first 10000 users can be fetched due to max offset = 9951.`); } } // Chỉ lấy user IDs, không lấy detailed info const currentUserIds = userListResponse.users.map((u) => u.user_id); userIds.push(...currentUserIds); console.log(`Fetched ${userIds.length}/${Math.min(totalUsers, 10000)} user IDs`); // Điều kiện dừng if (userListResponse.users.length === 0 || userIds.length >= Math.min(totalUsers, 10000) || userListResponse.users.length < count) { break; } offset += count; // Protection: Tránh infinite loop if (offset > 9951) { console.warn("Reached Zalo maximum offset limit (9951), stopping pagination"); break; } } console.log(`Successfully fetched ${userIds.length} user IDs`); return userIds; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } this.handleError(error, "Failed to get all user IDs"); } } /** * Get all users with advanced filtering based on UserInfo fields * Lấy tất cả users với bộ lọc nâng cao dựa trên các trường của UserInfo * * @param accessToken Access token của OA * @param filters Bộ lọc nâng cao * @returns Promise<UserInfo[]> - Danh sách users đã được lọc */ async getAllUsersWithAdvancedFilters(accessToken, filters = {}) { try { // Optimization: If only userIds filter is provided, get users directly const hasOnlyUserIdsFilter = filters.userIds && filters.userIds.length > 0 && Object.keys(filters).length === 1; if (hasOnlyUserIdsFilter) { console.log(`Fetching ${filters.userIds.length} specific users by IDs`); const userInfoPromises = filters.userIds.map((userId) => this.getUserInfo(accessToken, userId)); return await Promise.all(userInfoPromises); } // First get all users using basic Zalo API filters const basicFilters = { ...(filters.tag_name && { tag_name: filters.tag_name }), ...(filters.last_interaction_period && { last_interaction_period: filters.last_interaction_period, }), ...(filters.is_follower !== undefined && { is_follower: filters.is_follower, }), }; console.log("Fetching users with basic filters:", basicFilters); const allUsers = await this.getAllUsers(accessToken, basicFilters); // Apply advanced client-side filters const filteredUsers = allUsers.filter((user) => { // Filter by specific user IDs (exact match) - early return for performance if (filters.userIds && filters.userIds.length > 0) { if (!filters.userIds.includes(user.user_id)) { return false; } } // Filter by display name (contains) if (filters.display_name_contains && !user.display_name ?.toLowerCase() .includes(filters.display_name_contains.toLowerCase())) { return false; } // Filter by user alias (contains) if (filters.user_alias_contains && !user.user_alias ?.toLowerCase() .includes(filters.user_alias_contains.toLowerCase())) { return false; } // Filter by sensitive status if (filters.is_sensitive !== undefined && user.is_sensitive !== filters.is_sensitive) { return false; } // Filter by interaction date range if (filters.last_interaction_date_range) { const userDate = this.parseDate(user.user_last_interaction_date); const fromDate = this.parseDate(filters.last_interaction_date_range.from); const toDate = this.parseDate(filters.last_interaction_date_range.to); if (userDate < fromDate || userDate > toDate) { return false; } } // Filter by multiple tags (OR operation) if (filters.tag_names && filters.tag_names.length > 0) { const userTags = user.tags_and_notes_info.tag_names || []; const hasAnyTag = filters.tag_names.some((tag) => userTags.includes(tag)); if (!hasAnyTag) { return false; } } // Filter by required all tags (AND operation) if (filters.require_all_tags && filters.require_all_tags.length > 0) { const userTags = user.tags_and_notes_info.tag_names || []; const hasAllTags = filters.require_all_tags.every((tag) => userTags.includes(tag)); if (!hasAllTags) { return false; } } // Exclude users with specific tags if (filters.exclude_tags && filters.exclude_tags.length > 0) { const userTags = user.tags_and_notes_info.tag_names || []; const hasExcludedTag = filters.exclude_tags.some((tag) => userTags.includes(tag)); if (hasExcludedTag) { return false; } } // Filter by notes content (contains) if (filters.notes_contains) { const userNotes = user.tags_and_notes_info.notes || []; const hasNote = userNotes.some((note) => note.toLowerCase().includes(filters.notes_contains.toLowerCase())); if (!hasNote) { return false; } } // Shared info filters if (user.shared_info) { // Filter by city if (filters.city && user.shared_info.city?.toLowerCase() !== filters.city.toLowerCase()) { return false; } // Filter by district if (filters.district && user.shared_info.district?.toLowerCase() !== filters.district.toLowerCase()) { return false; } // Filter by phone (contains) if (filters.phone_contains && !user.shared_info.phone?.includes(filters.phone_contains)) { return false; } // Filter by name (contains) if (filters.name_contains && !user.shared_info.name ?.toLowerCase() .includes(filters.name_contains.toLowerCase())) { return false; } // Filter by address (contains) if (filters.address_contains && !user.shared_info.address ?.toLowerCase() .includes(filters.address_contains.toLowerCase())) { return false; } // Filter by date of birth range if (filters.dob_range && user.shared_info.user_dob) { const userDob = this.parseDate(user.shared_info.user_dob); const fromDob = this.parseDate(filters.dob_range.from); const toDob = this.parseDate(filters.dob_range.to); if (userDob < fromDob || userDob > toDob) { return false; } } // Filter by age range (calculated from dob) if (filters.age_range && user.shared_info.user_dob) { const userAge = this.calculateAge(user.shared_info.user_dob); if (userAge < filters.age_range.min || userAge > filters.age_range.max) { return false; } } } else { // If user has no shared_info, filter out based on shared_info requirements if (filters.city || filters.district || filters.phone_contains || filters.name_contains || filters.address_contains || filters.dob_range || filters.age_range) { return false; } } // Filter by external user ID (contains) if (filters.external_id_contains && !user.user_external_id?.includes(filters.external_id_contains)) { return false; } // Filter by has shared info if (filters.has_shared_info !== undefined && !!user.shared_info !== filters.has_shared_info) { return false; } // Filter by has phone if (filters.has_phone !== undefined && !!user.shared_info?.phone !== filters.has_phone) { return false; } // Filter by has address if (filters.has_address !== undefined && !!user.shared_info?.address !== filters.has_address) { return false; } return true; }); console.log(`Applied advanced filters: ${allUsers.length} -> ${filteredUsers.length} users`); return filteredUsers; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } this.handleError(error, "Failed to get users with advanced filters"); } } /** * Parse date from dd/MM/yyyy format to Date object * Helper method cho các filter date */ parseDate(dateString) { if (!dateString) return new Date(0); const [day, month, year] = dateString.split("/").map(Number); return new Date(year, month - 1, day); } /** * Calculate age from date of birth * Helper method cho age filter */ calculateAge(dobString) { if (!dobString) return 0; const dob = this.parseDate(dobString); const today = new Date(); let age = today.getFullYear() - dob.getFullYear(); const monthDiff = today.getMonth() - dob.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dob.getDate())) { age--; } return age; } /** * Cập nhật chi tiết người dùng * Endpoint: https://openapi.zalo.me/v3.0/oa/user/update * Method: POST * * @param accessToken OA access token * @param request Update user request with user_id and optional fields * @returns Promise<boolean> - true if successful */ async updateUser(accessToken, request) { try { const result = await this.client.apiPost(this.endpoints.user.update, accessToken, request); if (result.error !== 0) { throw new common_1.ZaloSDKError(result.message || "Failed to update user", result.error, result); } return true; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } throw new common_1.ZaloSDKError(`Failed to update user: ${error.message}`, -1, error); } } /** * Xóa thông tin người dùng * Endpoint: https://openapi.zalo.me/v2.0/oa/deletefollowerinfo * Method: POST * * @param accessToken OA access token * @param userId User ID to delete info for * @returns Promise<boolean> - true if successful * * Note: Chỉ xóa thông tin lưu trữ ở phía OA, không thay đổi tài khoản Zalo */ async deleteUserInfo(accessToken, userId) { try { const request = { user_id: userId }; const result = await this.client.apiPost(this.endpoints.user.delete, accessToken, request); if (result.error !== 0) { throw new common_1.ZaloSDKError(result.message || "Failed to delete user info", result.error, result); } return true; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } throw new common_1.ZaloSDKError(`Failed to delete user info: ${error.message}`, -1, error); } } /** * Truy xuất thông tin tùy biến của người dùng * Endpoint: https://openapi.zalo.me/v3.0/oa/user/detail/custominfo * Method: GET with data parameter as JSON string * * @param accessToken OA access token * @param request Get custom info request with user_id and optional fields_to_export * @returns Promise<UserCustomInfoResponse> - Custom info data * * Note: Tất cả giá trị trong custom_info đều được trả về dưới dạng string */ async getUserCustomInfo(accessToken, request) { try { // Build data object according to API spec const dataObject = { user_id: request.user_id, ...(request.fields_to_export && { fields_to_export: request.fields_to_export, }), }; // Send data as JSON string parameter according to API spec const params = { data: JSON.stringify(dataObject), }; const result = await this.client.apiGet(this.endpoints.user.getCustomInfo, accessToken, params); if (result.error !== 0) { throw new common_1.ZaloSDKError(result.message || "Failed to get user custom info", result.error, result); } return result; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } throw new common_1.ZaloSDKError(`Failed to get user custom info: ${error.message}`, -1, error); } } /** * Cập nhật thông tin tùy biến của người dùng * Endpoint: https://openapi.zalo.me/v3.0/oa/user/update/custominfo * Method: POST * * @param accessToken OA access token * @param request Update custom info request with user_id and custom_info * @returns Promise<boolean> - true if successful * * Note: Cấu trúc custom_info phụ thuộc vào thiết lập OA */ async updateUserCustomInfo(accessToken, request) { try { const result = await this.client.apiPost(this.endpoints.user.updateCustomInfo, accessToken, request); if (result.error !== 0) { throw new common_1.ZaloSDKError(result.message || "Failed to update user custom info", result.error, result); } return true; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } throw new common_1.ZaloSDKError(`Failed to update user custom info: ${error.message}`, -1, error); } } /** * Lấy danh sách nhãn * Endpoint: https://openapi.zalo.me/v2.0/oa/tag/gettagsofoa * Method: GET * * @param accessToken OA access token * @returns Promise<string[]> - Array of tag names */ async getLabels(accessToken) { try { const result = await this.client.apiGet(this.endpoints.tag.getList, accessToken); if (result.error !== 0) { throw new common_1.ZaloSDKError(result.message || "Failed to get labels", result.error, result); } return result.data || []; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } throw new common_1.ZaloSDKError(`Failed to get labels: ${error.message}`, -1, error); } } /** * Gắn nhãn người dùng * Endpoint: https://openapi.zalo.me/v2.0/oa/tag/tagfollower * * @param accessToken OA access token * @param request Tag user request with user_id and tag_name * @returns Promise<boolean> - true if successful * * Note: Nếu tag_name không tồn tại, API sẽ tự động tạo nhãn mới */ async addTagToUser(accessToken, request) { try { const result = await this.client.apiPost(this.endpoints.tag.addToUser, accessToken, request); if (result.error !== 0) { throw new common_1.ZaloSDKError(result.message || "Failed to add tag to user", result.error, result); } return true; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } throw new common_1.ZaloSDKError(`Failed to add tag to user: ${error.message}`, -1, error); } } /** * Gỡ nhãn người dùng * Endpoint: https://openapi.zalo.me/v2.0/oa/tag/rmfollowerfromtag * * @param accessToken OA access token * @param request Remove tag request with user_id and tag_name * @returns Promise<boolean> - true if successful */ async removeTagFromUser(accessToken, request) { try { const result = await this.client.apiPost(this.endpoints.tag.removeFromUser, accessToken, request); if (result.error !== 0) { throw new common_1.ZaloSDKError(result.message || "Failed to remove tag from user", result.error, result); } return true; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } throw new common_1.ZaloSDKError(`Failed to remove tag from user: ${error.message}`, -1, error); } } /** * Xóa nhãn * Endpoint: https://openapi.zalo.me/v2.0/oa/tag/rmtag * Method: POST * * @param accessToken OA access token * @param tagName Tag name to delete * @returns Promise<boolean> - true if successful */ async deleteLabel(accessToken, tagName) { try { const request = { tag_name: tagName }; const result = await this.client.apiPost(this.endpoints.tag.delete, accessToken, request); if (result.error !== 0) { throw new common_1.ZaloSDKError(result.message || "Failed to delete label", result.error, result); } return true; } catch (error) { if (error instanceof common_1.ZaloSDKError) { throw error; } throw new common_1.ZaloSDKError(`Failed to delete label: ${error.message}`, -1, error); } } /** * Get users by tag */ async getUsersByTag(accessToken, tagName, offset = 0, count = 50) { return this.getUserList(accessToken, { offset, count, tag_name: tagName, }); } /** * Get followers only */ async getFollowers(accessToken, offset = 0, count = 50) { return this.getUserList(accessToken, { offset, count, is_follower: true, }); } /** * Get users by interaction period */ async getUsersByInteraction(accessToken, period, offset = 0, count = 50) { return this.getUserList(accessToken, { offset, count, last_interaction_period: period, }); } /** * Bulk tag operations */ async bulkAddTag(accessToken, userIds, tagName) { const results = { success: [], failed: [], }; for (const userId of userIds) { try { await this.addTagToUser(accessToken, { user_id: userId, tag_name: tagName, }); results.success.push(userId); } catch (error) { results.failed.push(userId); } } return results; } /** * Bulk remove tag operations */ async bulkRemoveTag(accessToken, userIds, tagName) { const results = { success: [], failed: [], }; for (const userId of userIds) { try { await this.removeTagFromUser(accessToken, { user_id: userId, tag_name: tagName, }); results.success.push(userId); } catch (error) { results.failed.push(userId); } } return results; } handleError(error, message) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new common_1.ZaloSDKError(`${message}: ${errorMessage}`, -1, error); } } exports.UserService = UserService; //# sourceMappingURL=user.service.js.map