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
JavaScript
;
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);
}