UNPKG

n8n-nodes-mautic-advanced

Version:

Enhanced n8n node for Mautic with comprehensive API coverage including tags, campaigns, categories, and advanced contact management

676 lines (675 loc) 32.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.executeContactOperation = void 0; const n8n_workflow_1 = require("n8n-workflow"); const change_case_1 = require("change-case"); const ApiHelpers_1 = require("../utils/ApiHelpers"); const DataHelpers_1 = require("../utils/DataHelpers"); const GenericFunctions_1 = require("../GenericFunctions"); async function executeContactOperation(context, operation, i) { let responseData; try { switch (operation) { case 'create': responseData = await createContact(context, i); break; case 'update': responseData = await updateContact(context, i); break; case 'get': responseData = await getContact(context, i); break; case 'getAll': responseData = await getAllContacts(context, i); break; case 'delete': responseData = await deleteContact(context, i); break; case 'sendEmail': responseData = await sendEmailToContact(context, i); break; case 'editContactPoint': responseData = await editContactPoints(context, i); break; case 'editDoNotContactList': responseData = await editDoNotContactList(context, i); break; case 'addUtm': responseData = await addUtmTags(context, i); break; case 'removeUtm': responseData = await removeUtmTags(context, i); break; case 'getDevices': responseData = await getContactDevices(context, i); break; case 'getActivity': responseData = await getContactActivity(context, i); break; case 'getNotes': responseData = await getContactNotes(context, i); break; case 'getCompanies': responseData = await getContactCompanies(context, i); break; case 'getCampaigns': responseData = await getContactCampaigns(context, i); break; case 'getSegments': responseData = await getContactSegments(context, i); break; case 'addToSegments': responseData = await addContactToSegments(context, i); break; case 'removeFromSegments': responseData = await removeContactFromSegments(context, i); break; case 'addToCampaigns': responseData = await addContactToCampaigns(context, i); break; case 'removeFromCampaigns': responseData = await removeContactFromCampaigns(context, i); break; case 'getAllActivity': responseData = await getAllContactActivity(context, i); break; case 'getOwners': responseData = await getContactOwners(context); break; case 'getFields': responseData = await getContactFields(context); break; default: throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Operation '${operation}' is not supported for Contact resource.`, { itemIndex: i }); } return context.helpers.returnJsonArray((0, DataHelpers_1.wrapSingleItem)(responseData)); } catch (error) { return (0, ApiHelpers_1.handleApiError)(context, error, operation, 'Contact'); } } exports.executeContactOperation = executeContactOperation; async function createContact(context, itemIndex) { const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {}); const additionalFields = (0, ApiHelpers_1.getOptionalParam)(context, 'additionalFields', itemIndex, {}); const jsonActive = (0, ApiHelpers_1.getOptionalParam)(context, 'jsonParameters', itemIndex, false); let body = {}; if (!jsonActive) { body.email = (0, ApiHelpers_1.getOptionalParam)(context, 'email', itemIndex, ''); body.firstname = (0, ApiHelpers_1.getOptionalParam)(context, 'firstName', itemIndex, ''); body.lastname = (0, ApiHelpers_1.getOptionalParam)(context, 'lastName', itemIndex, ''); body.company = (0, ApiHelpers_1.getOptionalParam)(context, 'company', itemIndex, ''); body.position = (0, ApiHelpers_1.getOptionalParam)(context, 'position', itemIndex, ''); body.title = (0, ApiHelpers_1.getOptionalParam)(context, 'title', itemIndex, ''); } else { body = (0, DataHelpers_1.validateJsonParameter)(context, 'bodyJson', itemIndex); } addContactFields(body, additionalFields); // Data sanitization: Remove empty string values and validate email format const sanitizedBody = {}; Object.entries(body).forEach(([key, value]) => { // Skip empty strings as Mautic sometimes rejects them if (value !== '' && value !== null && value !== undefined) { sanitizedBody[key] = value; } }); // Basic email validation if email is provided if (sanitizedBody.email && typeof sanitizedBody.email === 'string') { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(sanitizedBody.email)) { throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid email format: ${sanitizedBody.email}`, { itemIndex }); } } // Log the sanitized body for debugging (only in development) if (process.env.NODE_ENV === 'development') { console.log('Mautic Contact Creation - Sanitized Body:', JSON.stringify(sanitizedBody, null, 2)); } const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', '/contacts/new', sanitizedBody); const contactData = [response.contact]; return (0, DataHelpers_1.processContactFields)(contactData, options); } async function updateContact(context, itemIndex) { const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {}); const updateFields = (0, ApiHelpers_1.getOptionalParam)(context, 'updateFields', itemIndex, {}); const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const body = {}; if (updateFields.email) body.email = updateFields.email; if (updateFields.firstName) body.firstname = updateFields.firstName; if (updateFields.lastName) body.lastname = updateFields.lastName; if (updateFields.company) body.company = updateFields.company; if (updateFields.position) body.position = updateFields.position; if (updateFields.title) body.title = updateFields.title; if (updateFields.bodyJson) { Object.assign(body, (0, DataHelpers_1.validateJsonParameter)(context, 'updateFields.bodyJson', itemIndex)); } addContactFields(body, updateFields); // Data sanitization: Remove empty string values (matching create behavior) const sanitizedBody = {}; Object.entries(body).forEach(([key, value]) => { if (value !== '' && value !== null && value !== undefined) { sanitizedBody[key] = value; } }); const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'PATCH', `/contacts/${contactId}/edit`, sanitizedBody); const contactData = [response.contact]; return (0, DataHelpers_1.processContactFields)(contactData, options); } async function getContact(context, itemIndex) { const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {}); const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/contacts/${contactId}`); const contactData = [response.contact]; const processedData = (0, DataHelpers_1.processContactFields)(contactData, options, options.fieldsToReturn); return (0, DataHelpers_1.convertNumericStrings)(processedData); } async function getAllContacts(context, itemIndex) { const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false); const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {}); const qs = (0, DataHelpers_1.buildQueryFromOptions)(options); // Build search expression from structured filters const searchParts = []; // Segment filter const segments = options.segments; if (Array.isArray(segments) && segments.length > 0) { const segmentMatchType = options.segmentMatchType || 'any'; const filterExpr = buildSearchFilterExpression(segments, 'segment', segmentMatchType); if (filterExpr) searchParts.push(filterExpr); } // Tag filter const tags = options.tags; if (Array.isArray(tags) && tags.length > 0) { const tagMatchType = options.tagMatchType || 'any'; const filterExpr = buildSearchFilterExpression(tags, 'tag', tagMatchType); if (filterExpr) searchParts.push(filterExpr); } // Owner filter — applied CLIENT-SIDE on the contact's owner.id. // Mautic's `owner:` search command matches the owner's first/last NAME (LIKE), not the user id, // so sending `owner:<id>` (what the Owner(s) dropdown provides) silently returns nothing. The v1 // contact body already includes `owner.id`, so we filter on that instead. const owners = options.owners; const ownerIdFilter = Array.isArray(owners) && owners.length > 0 ? new Set(owners.map((o) => Number(o)).filter((n) => Number.isFinite(n))) : undefined; // Do-Not-Contact filter — applied SERVER-SIDE via Mautic's `dnc:` search command, so only // matching contacts are fetched (replaces the previous client-side page-and-discard scan). // `dnc:<channel>` = IS on DNC for that channel; `dnc:any` = on any channel. const emailDncOnly = options.emailDncOnly === true; const smsDncOnly = options.smsDncOnly === true; const anyDncOnly = options.anyDncOnly === true; if (anyDncOnly) { searchParts.push('dnc:any'); } else { const dncTokens = []; if (emailDncOnly) dncTokens.push('dnc:email'); if (smsDncOnly) dncTokens.push('dnc:sms'); // Multiple specific channels = union (on email OR sms DNC); one token if only one is set. if (dncTokens.length === 1) searchParts.push(dncTokens[0]); else if (dncTokens.length > 1) searchParts.push(dncTokens.join(' OR ')); } // Stage filter (always OR for multiple stages - a contact can only be in one stage) const stages = options.stages; if (Array.isArray(stages) && stages.length > 0) { const filterExpr = buildSearchFilterExpression(stages, 'stage', 'any'); if (filterExpr) searchParts.push(filterExpr); } // Campaign filter (OR for multiple campaigns) const campaigns = options.campaigns; if (Array.isArray(campaigns) && campaigns.length > 0) { const filterExpr = buildSearchFilterExpression(campaigns, 'campaign', 'any'); if (filterExpr) searchParts.push(filterExpr); } // Combine all structured filters with AND if (searchParts.length > 0) { const structuredSearch = searchParts.length === 1 ? searchParts[0] : searchParts.map((part) => `(${part})`).join(' AND '); // Merge with raw search if provided const rawSearch = options.search; if (rawSearch && rawSearch.trim().length > 0) { qs.search = `${structuredSearch} AND (${rawSearch})`; } else { qs.search = structuredSearch; } } if (!qs.orderBy) qs.orderBy = 'id'; if (!qs.orderByDir) qs.orderByDir = 'asc'; if (qs.orderBy) { qs.orderBy = (0, change_case_1.snakeCase)(qs.orderBy); } const whereObj = options.where; if (whereObj && Array.isArray(whereObj.conditions)) { const filteredWhere = whereObj.conditions.filter((condition) => condition.col && condition.val !== undefined && condition.val !== ''); if (filteredWhere.length > 0) { qs.where = filteredWhere; } } let responseData; if (ownerIdFilter) { // Owner filter has no server-side equivalent, so page through the (already server-filtered) // results and keep only those whose owner.id matches, until the limit is met or results run out. const limit = returnAll ? undefined : (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30); responseData = await getContactsWithClientFilter(context, qs, (contact) => ownerIdFilter.has(Number(contact?.owner?.id)), limit); } else if (returnAll) { responseData = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'contacts', 'GET', '/contacts', {}, qs); } else { qs.limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30); const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/contacts', {}, qs); responseData = response.contacts ? Object.values(response.contacts) : []; } const processedData = (0, DataHelpers_1.processContactFields)(responseData, options, options.fieldsToReturn); return (0, DataHelpers_1.convertNumericStrings)(processedData); } async function deleteContact(context, itemIndex) { const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {}); const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); try { let responseData; const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'DELETE', `/contacts/${contactId}/delete`); if (response && response.contact !== undefined) { responseData = [response.contact]; } else { responseData = [{ success: true, message: 'Contact deleted successfully.' }]; } return (0, DataHelpers_1.processContactFields)(responseData, options); } catch (error) { throw error; } } async function sendEmailToContact(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const emailId = (0, ApiHelpers_1.getRequiredParam)(context, 'campaignEmailId', itemIndex); const tokensUi = (0, ApiHelpers_1.getOptionalParam)(context, 'tokensUi', itemIndex, {}); const assetAttachments = (0, ApiHelpers_1.getOptionalParam)(context, 'assetAttachments', itemIndex, ''); // Build request body const body = {}; // Process tokens from key-value pairs UI if (tokensUi?.tokenValues && Array.isArray(tokensUi.tokenValues)) { const tokens = {}; for (const tokenItem of tokensUi.tokenValues) { if (tokenItem.tokenKey && tokenItem.tokenValue !== undefined) { tokens[tokenItem.tokenKey] = tokenItem.tokenValue; } } if (Object.keys(tokens).length > 0) { body.tokens = tokens; } } // Process asset attachments if (assetAttachments && assetAttachments.trim()) { body.assetAttachments = assetAttachments .split(',') .map((id) => id.trim()) .filter((id) => id.length > 0); } // Only send body if it has content const requestBody = Object.keys(body).length > 0 ? body : {}; const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', `/emails/${emailId}/contact/${contactId}/send`, requestBody); return response; } async function editContactPoints(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const action = (0, ApiHelpers_1.getRequiredParam)(context, 'action', itemIndex); const points = (0, ApiHelpers_1.getRequiredParam)(context, 'points', itemIndex); const eventName = (0, ApiHelpers_1.getOptionalParam)(context, 'eventName', itemIndex, ''); const actionName = (0, ApiHelpers_1.getOptionalParam)(context, 'actionName', itemIndex, ''); const body = {}; if (eventName) body.eventName = eventName; if (actionName) body.actionName = actionName; const endpoint = action === 'add' ? `/contacts/${contactId}/points/plus/${points}` : `/contacts/${contactId}/points/minus/${points}`; const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', endpoint, body); return response; } async function editDoNotContactList(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const action = (0, ApiHelpers_1.getRequiredParam)(context, 'action', itemIndex); const channel = (0, ApiHelpers_1.getRequiredParam)(context, 'channel', itemIndex); const reason = (0, ApiHelpers_1.getOptionalParam)(context, 'reason', itemIndex, 3); // Default to Manual (3) const channelId = (0, ApiHelpers_1.getOptionalParam)(context, 'channelId', itemIndex, ''); const comments = (0, ApiHelpers_1.getOptionalParam)(context, 'comments', itemIndex, ''); const body = {}; if (reason !== undefined) body.reason = reason; if (channelId) body.channelId = channelId; if (comments) body.comments = comments; const endpoint = action === 'add' ? `/contacts/${contactId}/dnc/${channel}/add` : `/contacts/${contactId}/dnc/${channel}/remove`; const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', endpoint, body); return response.contact; } async function addUtmTags(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const utmFields = (0, ApiHelpers_1.getOptionalParam)(context, 'utmFields', itemIndex, {}); const body = {}; if (utmFields.utmSource) body.utm_source = utmFields.utmSource; if (utmFields.utmMedium) body.utm_medium = utmFields.utmMedium; if (utmFields.utmCampaign) body.utm_campaign = utmFields.utmCampaign; if (utmFields.utmContent) body.utm_content = utmFields.utmContent; if (utmFields.utmTerm) body.utm_term = utmFields.utmTerm; if (utmFields.userAgent) body.useragent = utmFields.userAgent; if (utmFields.url) body.url = utmFields.url; if (utmFields.referer) body.referer = utmFields.referer; if (utmFields.query) body.query = utmFields.query; if (utmFields.remoteHost) body.remotehost = utmFields.remoteHost; if (utmFields.lastActive) body.lastActive = utmFields.lastActive; const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', `/contacts/${contactId}/utm/add`, body); return response.contact; } async function removeUtmTags(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const utmId = (0, ApiHelpers_1.getRequiredParam)(context, 'utmId', itemIndex); const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', `/contacts/${contactId}/utm/${utmId}/remove`); return response.contact; } async function getContactDevices(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'devices', 'GET', `/contacts/${contactId}/devices`); return (0, DataHelpers_1.convertNumericStrings)(result); } async function getContactActivity(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {}); const qs = {}; const filters = {}; if (options.search) filters.search = options.search; if (options.includeEvents) filters.includeEvents = options.includeEvents.split(','); if (options.excludeEvents) filters.excludeEvents = options.excludeEvents.split(','); if (options.dateFrom) filters.dateFrom = options.dateFrom; if (options.dateTo) filters.dateTo = options.dateTo; qs['filters'] = filters; if (options.orderBy) qs.order = [options.orderBy, options.orderByDir ?? 'asc']; if (options.limit) qs.limit = options.limit; const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'events', 'GET', `/contacts/${contactId}/activity`, {}, qs); return (0, DataHelpers_1.convertNumericStrings)(result); } async function getContactNotes(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {}); const qs = {}; if (options.search) qs.search = options.search; if (options.orderBy) qs.orderBy = options.orderBy; if (options.orderByDir) qs.orderByDir = options.orderByDir; const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'notes', 'GET', `/contacts/${contactId}/notes`, {}, qs); return (0, DataHelpers_1.convertNumericStrings)(result); } async function getContactCompanies(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'companies', 'GET', `/contacts/${contactId}/companies`); return (0, DataHelpers_1.convertNumericStrings)(result); } async function getContactCampaigns(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'campaigns', 'GET', `/contacts/${contactId}/campaigns`); return (0, DataHelpers_1.convertNumericStrings)(result); } async function getContactSegments(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'segments', 'GET', `/contacts/${contactId}/segments`); return (0, DataHelpers_1.convertNumericStrings)(result); } async function addContactToSegments(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const segmentIdsRaw = (0, ApiHelpers_1.getRequiredParam)(context, 'segmentIds', itemIndex); const ids = segmentIdsRaw .split(',') .map((s) => s.trim()) .filter(Boolean); const results = []; for (const segmentId of ids) { const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', `/segments/${segmentId}/contact/${contactId}/add`); results.push(response); } return results; } async function removeContactFromSegments(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const segmentIdsRaw = (0, ApiHelpers_1.getRequiredParam)(context, 'segmentIds', itemIndex); const ids = segmentIdsRaw .split(',') .map((s) => s.trim()) .filter(Boolean); const results = []; for (const segmentId of ids) { const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', `/segments/${segmentId}/contact/${contactId}/remove`); results.push(response); } return results; } async function addContactToCampaigns(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const campaignIdsRaw = (0, ApiHelpers_1.getRequiredParam)(context, 'campaignIds', itemIndex); const ids = campaignIdsRaw .split(',') .map((s) => s.trim()) .filter(Boolean); const results = []; for (const campaignId of ids) { const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', `/campaigns/${campaignId}/contact/${contactId}/add`); results.push(response); } return results; } async function removeContactFromCampaigns(context, itemIndex) { const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex); const campaignIdsRaw = (0, ApiHelpers_1.getRequiredParam)(context, 'campaignIds', itemIndex); const ids = campaignIdsRaw .split(',') .map((s) => s.trim()) .filter(Boolean); const results = []; for (const campaignId of ids) { const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', `/campaigns/${campaignId}/contact/${contactId}/remove`); results.push(response); } return results; } async function getAllContactActivity(context, itemIndex) { const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {}); const qs = {}; const filters = {}; if (options.search) filters.search = options.search; if (options.includeEvents) filters.includeEvents = options.includeEvents.split(','); if (options.excludeEvents) filters.excludeEvents = options.excludeEvents.split(','); if (options.dateFrom) filters.dateFrom = options.dateFrom; if (options.dateTo) filters.dateTo = options.dateTo; qs['filters'] = filters; if (options.orderBy) qs.order = [options.orderBy, options.orderByDir ?? 'asc']; if (options.limit) qs.limit = options.limit; const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'events', 'GET', `/contacts/activity`, {}, qs); return (0, DataHelpers_1.convertNumericStrings)(result); } /** * Build a Mautic search filter expression from an array of values. * @param values Array of filter values (IDs, aliases, or names) * @param filterType The Mautic search filter type (e.g., 'segment', 'tag', 'owner', 'stage', 'campaign') * @param matchType 'any' for OR logic, 'all' for AND logic * @returns The search expression string, or empty string if no valid values */ function buildSearchFilterExpression(values, filterType, matchType) { const filters = values .map((value) => `${filterType}:${value}`) .filter((expr) => expr.trim().length > 0); if (filters.length === 0) { return ''; } if (filters.length === 1) { return filters[0]; } const operator = matchType === 'all' ? ' AND ' : ' OR '; return filters.join(operator); } function normalizeTagsInput(tagsInput) { // Handle different input formats for tags // If it's already an array of strings, return as is if (Array.isArray(tagsInput) && tagsInput.every((tag) => typeof tag === 'string')) { return tagsInput; } // If it's a string, split by comma if (typeof tagsInput === 'string') { return tagsInput .split(',') .map((tag) => tag.trim()) .filter((tag) => tag.length > 0); } // If it's an array of objects with 'tag' property if (Array.isArray(tagsInput) && tagsInput.every((item) => typeof item === 'object' && item.tag)) { return tagsInput.map((item) => item.tag); } // If it's a complex object (like user's input with inputA/inputB) if (typeof tagsInput === 'object' && tagsInput !== null) { const tags = []; // Handle inputA and inputB structure if (tagsInput.inputA && Array.isArray(tagsInput.inputA)) { tags.push(...tagsInput.inputA.map((item) => item.tag || item).filter(Boolean)); } if (tagsInput.inputB && Array.isArray(tagsInput.inputB)) { tags.push(...tagsInput.inputB.map((item) => item.tag || item).filter(Boolean)); } // If no inputA/inputB, try to extract from any array properties if (tags.length === 0) { Object.values(tagsInput).forEach((value) => { if (Array.isArray(value)) { tags.push(...value.map((item) => item.tag || item).filter(Boolean)); } }); } // Remove duplicates and return return [...new Set(tags)]; } // Fallback: return empty array return []; } function addContactFields(body, fields) { const addressUi = fields.addressUi; if (addressUi?.addressValues) { const { addressValues } = addressUi; body.address1 = addressValues.address1; body.address2 = addressValues.address2; body.city = addressValues.city; body.state = addressValues.state; body.country = addressValues.country; body.zipcode = addressValues.zipCode; } const socialMediaUi = fields.socialMediaUi; if (socialMediaUi?.socialMediaValues) { const { socialMediaValues } = socialMediaUi; const data = socialMediaValues.reduce((obj, value) => Object.assign(obj, { [`${value.socialMediaField}`]: value.value }), {}); Object.assign(body, data); } if (fields.company) body.company = fields.company; if (fields.position) body.position = fields.position; if (fields.ipAddress) body.ipAddress = fields.ipAddress; if (fields.lastActive) body.lastActive = fields.lastActive; if (fields.owner) body.owner = fields.owner; if (fields.perspective) body.perspective = fields.perspective; if (fields.points) body.points = fields.points; if (fields.preferredChannel) body.preferred_channel = fields.preferredChannel; if (fields.tags) body.tags = normalizeTagsInput(fields.tags); const customFieldsUi = fields.customFieldsUi; if (customFieldsUi?.customFieldValues) { const { customFieldValues } = customFieldsUi; const data = customFieldValues.reduce((obj, value) => Object.assign(obj, { [`${value.fieldId}`]: value.fieldValue }), {}); Object.assign(body, data); } } // Page through /contacts (already narrowed by any server-side search in `qs`) and keep only the // contacts that satisfy `predicate`, stopping once `limit` matches are collected or results run out. // Used for filters that have no server-side equivalent (e.g. owner-by-id). async function getContactsWithClientFilter(context, qs, predicate, limit) { const requestedStart = Number(qs.start ?? 0); const maxResults = typeof limit === 'number' && limit > 0 ? Math.floor(limit) : undefined; const contacts = []; let remaining = maxResults; let currentStart = Number.isFinite(requestedStart) && requestedStart > 0 ? requestedStart : 0; while (remaining === undefined || remaining > 0) { // Always fetch full pages so the scan terminates promptly; `remaining` only caps what we keep. const pageLimit = GenericFunctions_1.DEFAULT_MAUTIC_PAGE_SIZE; const pageQs = { ...qs, start: currentStart, limit: pageLimit }; const pageResponse = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/contacts', {}, pageQs); const pageContacts = pageResponse.contacts ? Object.values(pageResponse.contacts) : []; const filtered = pageContacts.filter(predicate); const toAdd = remaining === undefined ? filtered : filtered.slice(0, remaining); contacts.push(...toAdd); if (remaining !== undefined) { remaining -= toAdd.length; } if (pageContacts.length < pageLimit) break; currentStart += pageContacts.length; } return contacts; } async function getContactOwners(context) { const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/users'); return (0, DataHelpers_1.convertNumericStrings)(response); } async function getContactFields(context) { const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/contacts/list/fields'); return (0, DataHelpers_1.convertNumericStrings)(response); }