UNPKG

n8n-nodes-instantly

Version:
616 lines 26.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InstantlyApi = void 0; const n8n_workflow_1 = require("n8n-workflow"); const generic_functions_1 = require("../generic.functions"); const OperationRouter_1 = require("./operations/OperationRouter"); const LeadParameters_1 = require("./parameters/LeadParameters"); const CampaignParameters_1 = require("./parameters/CampaignParameters"); // Helper function to format dates for Instantly API (YYYY-MM-DD format) function formatDateForApi(dateInput) { if (!dateInput || dateInput === '') { return ''; } let dateString = String(dateInput); // Handle n8n DateTime objects that come as "[DateTime: 2025-06-26T13:52:08.271Z]" if (dateString.startsWith('[DateTime: ') && dateString.endsWith(']')) { dateString = dateString.slice(11, -1); // Remove "[DateTime: " and "]" } // Handle ISO datetime strings (e.g., "2025-06-19T13:52:45.316Z") if (dateString.includes('T')) { dateString = dateString.split('T')[0]; } // Handle date strings that might have time components separated by space if (dateString.includes(' ')) { dateString = dateString.split(' ')[0]; } // Validate the resulting date format (should be YYYY-MM-DD) const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateRegex.test(dateString)) { // Try to parse as Date and format try { const parsedDate = new Date(dateInput); if (!isNaN(parsedDate.getTime())) { // Format as YYYY-MM-DD const year = parsedDate.getFullYear(); const month = String(parsedDate.getMonth() + 1).padStart(2, '0'); const day = String(parsedDate.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } } catch (error) { console.warn('Failed to parse date:', dateInput, error); } // If all parsing fails, return empty string to avoid API errors console.warn('Invalid date format for Instantly API:', dateInput); return ''; } return dateString; } // Helper function for pagination async function paginateInstantlyApi(context, endpoint, resourceName) { let allItems = []; let startingAfter; let hasMore = true; let pageCount = 0; console.log(`Starting pagination for ${resourceName}...`); while (hasMore) { pageCount++; const queryParams = { limit: 100 }; // Use max limit for efficiency if (startingAfter) { queryParams.starting_after = startingAfter; } console.log(`Fetching page ${pageCount} with params:`, queryParams); const response = await generic_functions_1.instantlyApiRequest.call(context, 'GET', endpoint, {}, queryParams); console.log(`Page ${pageCount} response:`, { hasItems: !!response.items, itemsLength: response.items?.length || 0, hasNextStartingAfter: !!response.next_starting_after, nextStartingAfter: response.next_starting_after }); // Handle response structure - items are in 'items' array for paginated responses let itemsData = []; if (response.items && Array.isArray(response.items)) { itemsData = response.items; } else if (response.data && Array.isArray(response.data)) { itemsData = response.data; } else if (Array.isArray(response)) { itemsData = response; } else { console.warn('Unexpected response structure:', response); } if (itemsData.length > 0) { allItems = allItems.concat(itemsData); console.log(`Added ${itemsData.length} ${resourceName}. Total so far: ${allItems.length}`); } else { console.log(`No ${resourceName} found in this page, stopping pagination`); hasMore = false; } // Check if there are more pages using next_starting_after if (response.next_starting_after && itemsData.length > 0) { startingAfter = response.next_starting_after; console.log(`Next page cursor: ${startingAfter}`); } else { console.log('No more pages available'); hasMore = false; } // Safety check to prevent infinite loops if (pageCount > 1000) { console.warn('Pagination stopped after 1000 pages to prevent infinite loop'); break; } } console.log(`Pagination completed. Total ${resourceName} retrieved: ${allItems.length}`); return allItems; } // Helper function to get campaigns for dropdown async function getCampaigns(filter) { try { // Get campaigns using our existing pagination function const responseData = await generic_functions_1.instantlyApiRequest.call(this, 'GET', '/api/v2/campaigns', {}, { limit: 100 }); // Handle response structure - campaigns are in 'items' array for paginated responses let campaigns = []; if (responseData.items && Array.isArray(responseData.items)) { campaigns = responseData.items; } else if (Array.isArray(responseData)) { campaigns = responseData; } // Filter campaigns if filter is provided const filteredCampaigns = campaigns.filter((campaign) => { if (!filter) return true; const campaignName = `${campaign.name || ''}`.toLowerCase(); return campaignName.includes(filter.toLowerCase()); }); // Map campaigns to dropdown options const campaignOptions = filteredCampaigns.map((campaign) => ({ name: campaign.name || `Campaign ${campaign.id}`, value: campaign.id, })); return { results: campaignOptions, }; } catch (error) { console.error('Error fetching campaigns for dropdown:', error); return { results: [], }; } } // Helper function to get email accounts for dropdown async function getEmailAccounts(filter) { try { // Get email accounts using the accounts API endpoint const responseData = await generic_functions_1.instantlyApiRequest.call(this, 'GET', '/api/v2/accounts', {}, { limit: 100 }); // Handle response structure - accounts are in 'items' array for paginated responses let accounts = []; if (responseData.items && Array.isArray(responseData.items)) { accounts = responseData.items; } else if (Array.isArray(responseData)) { accounts = responseData; } // Filter accounts if filter is provided const filteredAccounts = accounts.filter((account) => { if (!filter) return true; const accountEmail = `${account.email || ''}`.toLowerCase(); const accountName = `${account.first_name || ''} ${account.last_name || ''}`.toLowerCase().trim(); return accountEmail.includes(filter.toLowerCase()) || accountName.includes(filter.toLowerCase()); }); // Map accounts to dropdown options const accountOptions = filteredAccounts.map((account) => { const displayName = account.first_name && account.last_name ? `${account.email} (${account.first_name} ${account.last_name})` : account.email; return { name: displayName || `Account ${account.id}`, value: account.email, }; }); return { results: accountOptions, }; } catch (error) { console.error('Error fetching email accounts for dropdown:', error); return { results: [], }; } } // Helper function to get email accounts for multiOptions selection async function getEmailAccountsForSelection() { console.log('🔍 getEmailAccountsForSelection called!'); try { // Get email accounts using the accounts API endpoint const responseData = await generic_functions_1.instantlyApiRequest.call(this, 'GET', '/api/v2/accounts', {}, { limit: 100 }); console.log('📧 Email accounts response:', responseData); // Handle response structure - accounts are in 'items' array for paginated responses let accounts = []; if (responseData.items && Array.isArray(responseData.items)) { accounts = responseData.items; } else if (Array.isArray(responseData)) { accounts = responseData; } console.log(`📋 Found ${accounts.length} email accounts`); // Map accounts to multiOptions format const accountOptions = accounts.map((account) => { const displayName = account.first_name && account.last_name ? `${account.email} (${account.first_name} ${account.last_name})` : account.email; return { name: displayName || `Account ${account.id}`, value: account.email, }; }); console.log('✅ Returning account options:', accountOptions); return accountOptions; } catch (error) { console.error('❌ Error fetching email accounts for selection:', error); return []; } } // Helper function to get leads for dropdown async function getLeads(filter) { try { // Get leads using the leads list API endpoint const body = { limit: 100, }; // Add search filter if provided if (filter) { body.search = filter; } const response = await generic_functions_1.instantlyApiRequest.call(this, 'POST', '/api/v2/leads/list', body); const leadOptions = []; if (response.items && Array.isArray(response.items)) { for (const lead of response.items) { const name = `${lead.first_name || ''} ${lead.last_name || ''}`.trim() || lead.email || 'Unknown Lead'; const value = lead.id; leadOptions.push({ name: `${name} (${lead.email || 'No email'})`, value, }); } } return { results: leadOptions, }; } catch (error) { console.error('Error fetching leads for dropdown:', error); return { results: [], }; } } class InstantlyApi { constructor() { this.description = { displayName: 'Instantly', name: 'instantly', icon: 'file:instantly.svg', group: ['output'], version: 1, subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}', description: 'Interact with Instantly API', defaults: { name: 'Instantly', }, inputs: ["main" /* NodeConnectionType.Main */], outputs: ["main" /* NodeConnectionType.Main */], credentials: [ { name: 'instantlyApi', required: true, }, ], properties: [ { displayName: 'Resource', name: 'resource', type: 'options', noDataExpression: true, options: [ { name: 'Account', value: 'account', }, { name: 'Analytics', value: 'analytics', }, { name: 'Campaign', value: 'campaign', }, { name: 'Lead', value: 'lead', }, ], default: 'campaign', }, // CAMPAIGN OPERATIONS - Using modular parameters ...CampaignParameters_1.campaignParameters, // LEAD OPERATIONS - Using modular parameters ...LeadParameters_1.leadParameters, // ACCOUNT OPERATIONS { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['account'], }, }, options: [ { name: 'Get Many Accounts', value: 'getMany', description: 'Get many email accounts', action: 'Get many email accounts', }, { name: 'Get Single Account', value: 'get', description: 'Get a single email account by email address', action: 'Get a single email account', }, ], default: 'getMany', }, // Email Account Selector { displayName: 'Email Account', name: 'emailAccount', type: 'resourceLocator', default: { mode: 'list', value: '' }, required: true, modes: [ { displayName: 'From List', name: 'list', type: 'list', placeholder: 'Select an email account...', typeOptions: { searchListMethod: 'getEmailAccounts', searchable: true, }, }, { displayName: 'By Email', name: 'email', type: 'string', placeholder: 'e.g. user@example.com', }, ], displayOptions: { show: { resource: ['account'], operation: ['get'], }, }, description: 'The email account to retrieve. Choose from the list, or specify an email address.', }, // Return All for accounts { displayName: 'Return All', name: 'returnAll', type: 'boolean', default: false, displayOptions: { show: { resource: ['account'], operation: ['getMany'], }, }, description: 'Whether to return all results or only up to a given limit', }, // Limit for accounts { displayName: 'Limit', name: 'limit', type: 'number', typeOptions: { minValue: 1, }, default: 50, displayOptions: { show: { resource: ['account'], operation: ['getMany'], returnAll: [false], }, }, description: 'Max number of results to return', }, // ANALYTICS OPERATIONS { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['analytics'], }, }, options: [ { name: 'Get Campaign Analytics', value: 'getCampaignAnalytics', description: 'Get analytics for a campaign', action: 'Get campaign analytics', }, ], default: 'getCampaignAnalytics', }, // Return All toggle for analytics { displayName: 'Return All', name: 'returnAll', type: 'boolean', default: false, displayOptions: { show: { resource: ['analytics'], operation: ['getCampaignAnalytics'], }, }, description: 'Whether to return analytics for all campaigns or just the selected campaign', }, // Campaign Selector for analytics { displayName: 'Campaign', name: 'campaignId', type: 'resourceLocator', default: { mode: 'list', value: '' }, required: true, modes: [ { displayName: 'From List', name: 'list', type: 'list', placeholder: 'Select a campaign...', typeOptions: { searchListMethod: 'getCampaigns', searchable: true, }, }, { displayName: 'By ID', name: 'id', type: 'string', placeholder: 'e.g. 01234567-89ab-cdef-0123-456789abcdef', }, ], displayOptions: { show: { resource: ['analytics'], operation: ['getCampaignAnalytics'], returnAll: [false], }, }, description: 'The campaign to get analytics for. Choose from the list, or specify an ID.', }, // Date Range Fields for Analytics { displayName: 'Start Date', name: 'startDate', type: 'dateTime', default: '', displayOptions: { show: { resource: ['analytics'], operation: ['getCampaignAnalytics'], }, }, description: 'Start date for analytics data (YYYY-MM-DD format). Leave empty for all-time analytics.', placeholder: 'e.g. 2024-01-01', }, { displayName: 'End Date', name: 'endDate', type: 'dateTime', default: '', displayOptions: { show: { resource: ['analytics'], operation: ['getCampaignAnalytics'], }, }, description: 'End date for analytics data (YYYY-MM-DD format). Leave empty for all-time analytics.', placeholder: 'e.g. 2024-12-31', }, ], }; this.methods = { listSearch: { getCampaigns, getEmailAccounts, getLeads, }, loadOptions: { getEmailAccountsForSelection, }, }; } async execute() { const items = this.getInputData(); const returnData = []; // Helper function to extract campaign ID from resourceLocator const getCampaignId = (i) => { const campaignLocator = this.getNodeParameter('campaignId', i); if (typeof campaignLocator === 'string') { // Backward compatibility - if it's still a string return campaignLocator; } // Extract value from resourceLocator return campaignLocator.value || campaignLocator; }; // Helper function to extract email from resourceLocator const getEmailAccount = (i) => { const emailLocator = this.getNodeParameter('emailAccount', i); if (typeof emailLocator === 'string') { // Backward compatibility - if it's still a string return emailLocator; } // Extract value from resourceLocator return emailLocator.value || emailLocator; }; for (let i = 0; i < items.length; i++) { try { const resource = this.getNodeParameter('resource', i); const operation = this.getNodeParameter('operation', i); let responseData; if (resource === 'campaign') { // Use the modular OperationRouter for Campaign operations responseData = await OperationRouter_1.OperationRouter.execute(this, i, resource, operation); } else if (resource === 'lead') { // Use the modular OperationRouter for Lead operations responseData = await OperationRouter_1.OperationRouter.execute(this, i, resource, operation); } else if (resource === 'account') { if (operation === 'getMany') { const returnAll = this.getNodeParameter('returnAll', i, false); const limit = this.getNodeParameter('limit', i, 50); // Validate limit doesn't exceed 100 if (limit > 100) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Limit cannot exceed 100. Instantly API has a maximum limit of 100.', { itemIndex: i }); } if (returnAll) { // Get all accounts with pagination const allAccounts = await paginateInstantlyApi(this, '/api/v2/accounts', 'accounts'); responseData = { items: allAccounts }; } else { // Get single page with specified limit const queryParams = { limit }; responseData = await generic_functions_1.instantlyApiRequest.call(this, 'GET', '/api/v2/accounts', {}, queryParams); } } else if (operation === 'get') { const emailAccount = getEmailAccount(i); responseData = await generic_functions_1.instantlyApiRequest.call(this, 'GET', `/api/v2/accounts/${emailAccount}`); } } else if (resource === 'analytics') { if (operation === 'getCampaignAnalytics') { const returnAll = this.getNodeParameter('returnAll', i, false); // Get date parameters and format them for the API const startDateInput = this.getNodeParameter('startDate', i, ''); const endDateInput = this.getNodeParameter('endDate', i, ''); // Build query parameters object const queryParams = {}; // Add date parameters if provided, using the robust date formatter const formattedStartDate = formatDateForApi(startDateInput); const formattedEndDate = formatDateForApi(endDateInput); if (formattedStartDate) { queryParams.start_date = formattedStartDate; } if (formattedEndDate) { queryParams.end_date = formattedEndDate; } if (returnAll) { // Get analytics for all campaigns with date filtering responseData = await generic_functions_1.instantlyApiRequest.call(this, 'GET', '/api/v2/campaigns/analytics', {}, queryParams); } else { // Get analytics for specific campaign with date filtering const campaignId = getCampaignId(i); queryParams.id = campaignId; responseData = await generic_functions_1.instantlyApiRequest.call(this, 'GET', '/api/v2/campaigns/analytics', {}, queryParams); } } } const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { itemData: { item: i } }); returnData.push(...executionData); } catch (error) { if (this.continueOnFail()) { const executionData = this.helpers.constructExecutionMetaData([{ json: { error: error.message } }], { itemData: { item: i } }); returnData.push(...executionData); continue; } throw error; } } return [returnData]; } } exports.InstantlyApi = InstantlyApi; //# sourceMappingURL=InstantlyApi.node.js.map