import type { BlipClient } from '../client.ts' import { type Identity, Node } from '../types/node.ts' import type { WhatsAppTemplateLanguage } from '../types/whatsapp.ts' import { uri } from '../utils/uri.ts' import { type ConsumeOptions, Namespace, type SendCommandOptions } from './namespace.ts' type AudienceSummary = { id: string name: string campaignStatus: string failed: number processed: number read: number received: number total: number scheduled?: string created: string } export class ActiveCampaignNamespace extends Namespace { constructor(blipClient: BlipClient, defaultOptions?: SendCommandOptions) { super(blipClient, 'activecampaign', defaultOptions) } public createAndDispatchBatchCampaign( campaign: { name: string flowId: string stateId: string campaignSender: string subbotIdentity?: Identity agentEmail?: string scheduled?: string tags?: Array canSendWithOpenTicket?: boolean }, audiences: Array<{ recipient: string buttonVariable?: string mediaVariable?: string bodyVariables: Array additionalContactExtras?: Record }>, message: { template: string language: WhatsAppTemplateLanguage }, opts?: ConsumeOptions, ): Promise<{ id: string status: string created: string }> { const messageVariables = [ audiences[0].mediaVariable ? 'urllink' : undefined, audiences[0].buttonVariable ? 'button' : undefined, ...audiences[0].bodyVariables.map((_, i) => `variable${i + 1}`), ].filter((v) => v) return this.sendCommand( { method: 'set', uri: uri`/campaign/full`, type: 'application/vnd.iris.activecampaign.full-campaign+json', resource: { campaign: { name: campaign.name, campaignType: 'BATCH', flowId: campaign.flowId, stateId: campaign.stateId, campaignSender: campaign.campaignSender, masterstate: campaign.subbotIdentity, attendanceRedirect: campaign.agentEmail ? new Node(campaign.agentEmail, 'blip.ai') : undefined, scheduled: campaign.scheduled, tags: campaign.tags, canSendWithOpenTicket: campaign.canSendWithOpenTicket, }, audiences: audiences.map((audience) => { return { recipient: audience.recipient, messageParams: { urllink: audience.mediaVariable, button: audience.buttonVariable, ...Object.fromEntries(audience.bodyVariables.map((v, i) => [`variable${i + 1}`, v])), ...audience.additionalContactExtras, }, } }), message: { messageTemplate: message.template, messageTemplateLanguage: message.language, // Needed because of a bug where the API interprets empty array as a single empty variable // E.g. "messageParams": [""] messageParams: messageVariables.length > 0 ? messageVariables : undefined, }, }, }, opts, ) } public async cancelCampaign(campaign: string, opts?: ConsumeOptions) { return await this.sendCommand( { method: 'delete', uri: uri`/campaigns/${campaign}`, }, opts, ) } public async getCampaigns(filter?: { tags: Array }, opts?: ConsumeOptions) { // This is needed because this route throws an error if passed an $skip or $take query parameter const resource = await this.sendCommand<'get', { items: Array<{ id: string; name: string }> }>( { method: 'get', uri: uri`/campaigns?${filter?.tags ? { tags: filter.tags.join(',') } : undefined}`, }, opts, ) return resource.items } public getCampaign( campaign: string, opts?: ConsumeOptions, ): Promise<{ id: string name: string campaignType: 'BATCH' | 'INDIVIDUAL' masterState: Identity flowId: string stateId: string campaignSender: Identity status: string created: string }> { return this.sendCommand( { method: 'get', uri: uri`/campaigns/${campaign}`, }, opts, ) } public async getCampaignMessage(campaign: string, opts?: ConsumeOptions) { const [message] = await this.sendCommand< 'get', Array<{ channelType: 'WHATSAPP' messageTemplate: string messageTemplateLanguage: WhatsAppTemplateLanguage messageParams: Array }> >( { method: 'get', uri: uri`/messages/${campaign}`, }, { collection: true, ...opts, }, ) return message } public getAudienceSummaries( filter?: { scheduled?: boolean campaignName?: string }, opts?: ConsumeOptions, ): Promise> { return this.sendCommand( { method: 'get', uri: uri`/audience-summary?${filter}`, }, { collection: true, ...opts, }, ) } public async getAudienceSummary(campaign: string, opts?: ConsumeOptions) { const [summary] = await this.sendCommand<'get', Array>( { method: 'get', uri: uri`/audience-summary/${campaign}`, }, { collection: true, ...opts, }, ) return summary } public getAudience( campaign: string, opts?: ConsumeOptions, ): Promise< Array<{ OwnerIdentity: Identity CampaignId: string recipient: string recipientType: 'PhoneNumber' channelType: 'WHATSAPP' messageParams: Record status: string validatedAccount: Identity // These strings are actually timestamps // Can be used to infer the message's state processed?: string received?: string read?: string failed?: string reasonCode?: number reasonDescription?: string }> > { return this.sendCommand( { method: 'get', uri: uri`/audiences/${campaign}`, }, { collection: true, take: 1000, ...opts, }, ) } /** * @param filters.createdDate Only audiences created on this date or after will be returned. Required. */ public getCampaignsSummaries( filters: { createdDate: string | Date source?: 'Portal' | 'Desk' | 'API' campaignSender?: string tags?: Array }, opts?: ConsumeOptions, ): Promise< Array<{ id: string name: string messageTemplate: string masterState: Identity flowId: string stateId: string attendanceRedirect: Identity campaignSender: Identity sendDate: string statusAudience: Array<{ recipientIdentity: Identity status: 'NOT_PROCESSED' | 'PROCESSED' | 'RECEIVED' | 'READ' | 'FAILED' processed?: string received?: string read?: string failed?: string reasonCode?: number reasonDescription?: string numberStatus: string }> }> > { return this.sendCommand( { method: 'get', uri: uri`/campaigns/summaries?${{ created: typeof filters.createdDate === 'string' ? filters.createdDate : filters.createdDate.toISOString(), SourceApplication: filters.source, CampaignSender: filters.campaignSender, tags: filters.tags?.join(','), }}`, }, { collection: true, ...opts, }, ) } }