chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:34:48 +08:00
commit 77bb5bf71f
3762 changed files with 353249 additions and 0 deletions
@@ -0,0 +1,83 @@
import { z } from '@botpress/sdk'
import { companySchema } from '../../definitions/actions/company'
import { getAuthenticatedHubspotClient, propertiesEntriesToRecord } from '../utils'
import * as bp from '.botpress'
type HubspotClient = Awaited<ReturnType<typeof getAuthenticatedHubspotClient>>
type HsCompany = NonNullable<Awaited<ReturnType<HubspotClient['searchCompany']>>>
type BpCompany = z.infer<typeof companySchema>
const _mapHsCompanyToBpCompany = (hsCompany: HsCompany): BpCompany => ({
id: hsCompany.id,
name: hsCompany.properties['name'] ?? '',
domain: hsCompany.properties['domain'] ?? '',
createdAt: hsCompany.createdAt.toISOString(),
updatedAt: hsCompany.updatedAt.toISOString(),
properties: hsCompany.properties,
})
const _getCompanyPropertyKeys = async (hsClient: HubspotClient) => {
const properties = await hsClient.getAllObjectProperties('companies')
return properties.results.map((property) => property.name)
}
export const searchCompany: bp.IntegrationProps['actions']['searchCompany'] = async ({
client,
ctx,
input,
logger,
}) => {
const hsClient = await getAuthenticatedHubspotClient({ client, ctx, logger })
const propertyKeys = await _getCompanyPropertyKeys(hsClient)
const company = await hsClient.searchCompany({
name: input.name,
domain: input.domain,
propertiesToReturn: propertyKeys,
})
return {
company: company ? _mapHsCompanyToBpCompany(company) : undefined,
}
}
export const getCompany: bp.IntegrationProps['actions']['getCompany'] = async ({ ctx, client, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ ctx, client, logger })
const propertyKeys =
input.propertiesToReturn && input.propertiesToReturn.length > 0
? input.propertiesToReturn
: await _getCompanyPropertyKeys(hsClient)
const company = await hsClient.getCompanyById({
companyId: Number(input.companyId),
propertiesToReturn: propertyKeys,
})
return {
company: _mapHsCompanyToBpCompany(company),
}
}
export const updateCompany: bp.IntegrationProps['actions']['updateCompany'] = async ({
ctx,
client,
input,
logger,
}) => {
const hsClient = await getAuthenticatedHubspotClient({ ctx, client, logger })
const additionalProperties = propertiesEntriesToRecord(input.properties ?? [])
const updatedCompany = await hsClient.updateCompany({
companyId: Number(input.companyId),
additionalProperties,
})
return {
company: {
..._mapHsCompanyToBpCompany(updatedCompany),
name: updatedCompany.properties['name'] ?? undefined,
domain: updatedCompany.properties['domain'] ?? undefined,
},
}
}
+175
View File
@@ -0,0 +1,175 @@
import { z } from '@botpress/sdk'
import { contactSchema } from '../../definitions/actions/contact'
import { buildContactUrl, getAuthenticatedHubspotClient, getOrFetchPortalId, propertiesEntriesToRecord } from '../utils'
import * as bp from '.botpress'
type HubspotClient = Awaited<ReturnType<typeof getAuthenticatedHubspotClient>>
type HsContact = Awaited<ReturnType<HubspotClient['getContact']>>
type BpContact = z.infer<typeof contactSchema>
const _mapHsContactToBpContact = (hsContact: HsContact): BpContact => ({
id: hsContact.id,
properties: hsContact.properties,
email: hsContact.properties['email'] ?? '',
phone: hsContact.properties['phone'] ?? '',
createdAt: hsContact.createdAt.toISOString(),
updatedAt: hsContact.updatedAt.toISOString(),
})
const _getContactPropertyKeys = async (hsClient: HubspotClient) => {
const properties = await hsClient.getAllObjectProperties('contacts')
return properties.results.map((property) => property.name)
}
export const searchContact: bp.IntegrationProps['actions']['searchContact'] = async ({
client,
ctx,
input,
logger,
}) => {
const hsClient = await getAuthenticatedHubspotClient({ ctx, client, logger })
const phoneStr = input.phone ? `phone ${input.phone}` : 'unknown phone'
const emailStr = input.email ? `email ${input.email}` : 'unknown email'
const infosStr = `${phoneStr} and ${emailStr}`
const propertyKeys = input.properties?.length ? input.properties : await _getContactPropertyKeys(hsClient)
logger
.forBot()
.debug(
`Searching for contact with ${infosStr} ${propertyKeys?.length ? `and properties ${propertyKeys?.join(', ')}` : ''}`
)
const contact = await hsClient.searchContact({
phone: input.phone,
email: input.email,
propertiesToReturn: propertyKeys,
})
if (!contact) {
return {
contact: undefined,
}
}
const portalId = await getOrFetchPortalId({ client, ctx, hsClient })
return {
contact: _mapHsContactToBpContact(contact),
url: buildContactUrl({ portalId, contactId: contact.id }),
}
}
export const listContactProperties: bp.IntegrationProps['actions']['listContactProperties'] = async ({
ctx,
client,
logger,
}) => {
const hsClient = await getAuthenticatedHubspotClient({ ctx, client, logger })
try {
const properties = await hsClient.listContactProperties()
return {
properties: properties.map((property) => ({
name: property.name,
label: property.label,
type: property.type,
fieldType: property.fieldType,
groupName: property.groupName,
description: property.description,
...(property.referencedObjectType ? { referencedObjectType: property.referencedObjectType } : {}),
})),
}
} catch (err: unknown) {
logger.forBot().debug(`Contact properties could not be retrieved: ${err}`)
return { properties: [] }
}
}
export const createContact: bp.IntegrationProps['actions']['createContact'] = async ({
ctx,
client,
input,
logger,
}) => {
const hsClient = await getAuthenticatedHubspotClient({ ctx, client, logger })
const { email, phone, owner, companies, tickets, properties } = input
const additionalProperties = propertiesEntriesToRecord(properties ?? [])
const newContact = await hsClient.createContact({
email,
phone,
ownerEmailOrId: owner,
companies,
ticketIds: tickets,
additionalProperties,
})
return {
contact: _mapHsContactToBpContact(newContact),
}
}
export const getContact: bp.IntegrationProps['actions']['getContact'] = async ({ ctx, client, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ ctx, client, logger })
const propertyKeys = await _getContactPropertyKeys(hsClient)
const [contact, portalId] = await Promise.all([
hsClient.getContact({
contactId: input.contactIdOrEmail,
propertiesToReturn: propertyKeys,
}),
getOrFetchPortalId({ client, ctx, hsClient }),
])
return {
contact: _mapHsContactToBpContact(contact),
url: buildContactUrl({ portalId, contactId: contact.id }),
}
}
export const updateContact: bp.IntegrationProps['actions']['updateContact'] = async ({
ctx,
client,
input,
logger,
}) => {
const hsClient = await getAuthenticatedHubspotClient({ ctx, client, logger })
const { contactIdOrEmail, phone, email, properties } = input
const additionalProperties = propertiesEntriesToRecord(properties ?? [])
const updatedContact = await hsClient.updateContact({
contactId: contactIdOrEmail,
additionalProperties,
email,
phone,
})
return {
contact: {
..._mapHsContactToBpContact(updatedContact),
email: updatedContact.properties['email'] ?? undefined,
phone: updatedContact.properties['phone'] ?? undefined,
},
}
}
export const deleteContact: bp.IntegrationProps['actions']['deleteContact'] = async ({
ctx,
client,
input,
logger,
}) => {
const hsClient = await getAuthenticatedHubspotClient({ ctx, client, logger })
await hsClient.deleteContact({
contactId: input.contactId,
})
return {}
}
export const listContacts: bp.IntegrationProps['actions']['listContacts'] = async ({ ctx, client, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ ctx, client, logger })
const propertyKeys = await _getContactPropertyKeys(hsClient)
const { contacts, nextToken } = await hsClient.listContacts({
properties: propertyKeys,
nextToken: input.meta.nextToken,
})
return {
contacts: contacts.map(_mapHsContactToBpContact),
meta: {
nextToken,
},
}
}
+87
View File
@@ -0,0 +1,87 @@
import { z } from '@botpress/sdk'
import { dealSchema } from '../../definitions/actions/deal'
import { getAuthenticatedHubspotClient, propertiesEntriesToRecord } from '../utils'
import * as bp from '.botpress'
type HubspotClient = Awaited<ReturnType<typeof getAuthenticatedHubspotClient>>
type HsDeal = Awaited<ReturnType<HubspotClient['getDealById']>>
type BpDeal = z.infer<typeof dealSchema>
const _mapHsDealToBpDeal = (hsDeal: HsDeal): BpDeal => ({
id: hsDeal.id,
name: hsDeal.properties.dealname ?? '',
createdAt: hsDeal.createdAt.toISOString(),
updatedAt: hsDeal.updatedAt.toISOString(),
properties: hsDeal.properties,
})
const _getDealPropertyKeys = async (hsClient: HubspotClient) => {
const properties = await hsClient.getAllObjectProperties('deals')
return properties.results.map((property) => property.name)
}
export const searchDeal: bp.IntegrationProps['actions']['searchDeal'] = async ({ client, ctx, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ client, ctx, logger })
const propertyKeys = await _getDealPropertyKeys(hsClient)
const deal = await hsClient.searchDeal({ name: input.name, propertiesToReturn: propertyKeys })
if (!deal) {
return {
deal: undefined,
}
}
return {
deal: _mapHsDealToBpDeal(deal),
}
}
export const createDeal: bp.IntegrationProps['actions']['createDeal'] = async ({ client, ctx, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ client, ctx, logger })
const deal = await hsClient.createDeal({
name: input.name,
properties: propertiesEntriesToRecord(input.properties ?? []),
})
return {
deal: _mapHsDealToBpDeal(deal),
}
}
export const getDeal: bp.IntegrationProps['actions']['getDeal'] = async ({ client, ctx, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ client, ctx, logger })
const propertyKeys = await _getDealPropertyKeys(hsClient)
const deal = await hsClient.getDealById({ dealId: input.dealId, propertiesToReturn: propertyKeys })
return {
deal: _mapHsDealToBpDeal(deal),
}
}
export const updateDeal: bp.IntegrationProps['actions']['updateDeal'] = async ({ client, ctx, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ client, ctx, logger })
const deal = await hsClient.updateDealById({
dealId: input.dealId,
name: input.name,
properties: propertiesEntriesToRecord(input.properties ?? []),
})
return {
deal: {
..._mapHsDealToBpDeal(deal),
name: deal.properties.dealname ?? undefined,
},
}
}
export const deleteDeal: bp.IntegrationProps['actions']['deleteDeal'] = async ({ client, ctx, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ client, ctx, logger })
await hsClient.deleteDealById({ dealId: input.dealId })
return {}
}
+13
View File
@@ -0,0 +1,13 @@
import { getAuthenticatedHubspotClient } from '../utils'
import * as bp from '.botpress'
export const getFileUrl: bp.IntegrationProps['actions']['getFileUrl'] = async ({ ctx, client, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ ctx, client, logger })
try {
const url = await hsClient.getFileUrl({ fileName: input.fileName })
return { url }
} catch (err: unknown) {
logger.forBot().debug(`File ${input.fileName} URL could not be retrieved: ${err}`)
return { url: undefined }
}
}
+138
View File
@@ -0,0 +1,138 @@
import { RuntimeError } from '@botpress/sdk'
import { randomUUID } from 'crypto'
import { getHitlClient } from '../hitl/client'
import * as bp from '.botpress'
export const createUser: bp.IntegrationProps['actions']['createUser'] = async ({ client, input, logger }) => {
try {
const { name = 'Unknown', email, pictureUrl } = input
if (!email || !email.trim()) {
throw new RuntimeError('An identifier (email or phone number) is required for HITL user creation.')
}
const trimmedIdentifier = email.trim()
const isEmail = trimmedIdentifier.includes('@')
const contactType = isEmail ? ('email' as const) : ('phone' as const)
const userTags: Record<string, string> = { contactType }
if (isEmail) {
userTags.email = trimmedIdentifier
} else {
userTags.phoneNumber = trimmedIdentifier
}
const { user: botpressUser } = await client.getOrCreateUser({
name,
pictureUrl,
tags: userTags,
})
await client.setState({
id: botpressUser.id,
type: 'user',
name: 'hitlUserInfo',
payload: {
name,
contactIdentifier: trimmedIdentifier,
contactType,
},
})
logger.forBot().debug(`createUser: created/found user ${botpressUser.id} (${contactType})`)
return { userId: botpressUser.id }
} catch (thrown: unknown) {
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
throw new RuntimeError(error.message)
}
}
export const startHitl: bp.IntegrationProps['actions']['startHitl'] = async ({ ctx, client, logger, input }) => {
const hitlClient = getHitlClient(ctx, client, logger)
try {
const { userId, title, description = 'No description available' } = input
const {
state: { payload: userInfo },
} = await client.getState({ id: userId, name: 'hitlUserInfo', type: 'user' })
if (!userInfo?.contactIdentifier) {
throw new RuntimeError('User identifier not found. Please ensure the user is created with createUser first.')
}
const channelState = await client
.getState({ id: ctx.integrationId, name: 'hitlConfig', type: 'integration' })
.catch(() => null)
const channelInfo = channelState?.state?.payload
if (!channelInfo?.channelId) {
throw new RuntimeError(
'HITL channel not configured. Please make sure you enabled/configured HITL for this Integration.'
)
}
const { name, contactIdentifier } = userInfo
const { channelId, defaultInboxId, channelAccounts } = channelInfo
const inboxId = input.hitlSession?.inboxId?.length ? input.hitlSession.inboxId : defaultInboxId
const channelAccountId = channelAccounts?.[inboxId]
if (!channelAccountId) {
throw new RuntimeError(
`No channel account found for inbox ${inboxId}. Make sure this inbox was selected during setup.`
)
}
const integrationThreadId = randomUUID()
const result = await hitlClient.createConversation(
channelId,
channelAccountId,
integrationThreadId,
name,
contactIdentifier,
title || 'New Support Request',
description
)
const hubspotConversationId = result.data.conversationsThreadId
const { conversation } = await client.createConversation({
channel: 'hitl',
tags: {
id: hubspotConversationId,
userId,
integrationThreadId,
inboxId,
},
})
return { conversationId: conversation.id }
} catch (thrown: unknown) {
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
logger.forBot().error(`startHitl error: ${error.message}`)
throw new RuntimeError(error.message)
}
}
export const stopHitl: bp.IntegrationProps['actions']['stopHitl'] = async ({ ctx, client, input, logger }) => {
const { conversationId } = input
try {
const { conversation } = await client.getConversation({ id: conversationId })
const threadId = conversation.tags.id
if (!threadId) {
logger.forBot().warn(`stopHitl: no HubSpot thread ID on conversation ${conversationId} — skipping close`)
return {}
}
const hitlClient = getHitlClient(ctx, client, logger)
await hitlClient.closeThread(threadId)
logger.forBot().info(`stopHitl: closed HubSpot thread ${threadId}`)
} catch (thrown: unknown) {
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
logger.forBot().error(`stopHitl error: ${error.message}`)
throw new RuntimeError(error.message)
}
return {}
}
+22
View File
@@ -0,0 +1,22 @@
import * as companyActions from './company'
import * as contactActions from './contact'
import * as dealActions from './deal'
import * as fileActions from './file'
import { createUser, startHitl, stopHitl } from './hitl'
import * as leadActions from './lead'
import * as ownerActions from './owner'
import * as ticketActions from './ticket'
import * as bp from '.botpress'
export default {
...contactActions,
...dealActions,
...ticketActions,
...leadActions,
...companyActions,
...ownerActions,
...fileActions,
createUser,
startHitl,
stopHitl,
} as const satisfies bp.IntegrationProps['actions']
+88
View File
@@ -0,0 +1,88 @@
import { z } from '@botpress/sdk'
import { leadSchema } from '../../definitions/actions/lead'
import { getAuthenticatedHubspotClient, propertiesEntriesToRecord } from '../utils'
import * as bp from '.botpress'
type HubspotClient = Awaited<ReturnType<typeof getAuthenticatedHubspotClient>>
type HsLead = Awaited<ReturnType<HubspotClient['getLeadById']>>
type BpLead = z.infer<typeof leadSchema>
const _mapHsLeadToBpLead = (hsLead: HsLead): BpLead => ({
id: hsLead.id,
name: hsLead.properties.hs_lead_name ?? '',
createdAt: hsLead.createdAt.toISOString(),
updatedAt: hsLead.updatedAt.toISOString(),
properties: hsLead.properties,
})
const _getLeadPropertyKeys = async (hsClient: HubspotClient) => {
const properties = await hsClient.getAllObjectProperties('leads')
return properties.results.map((property) => property.name)
}
export const searchLead: bp.IntegrationProps['actions']['searchLead'] = async ({ client, ctx, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ client, ctx, logger })
const propertyKeys = await _getLeadPropertyKeys(hsClient)
const lead = await hsClient.searchLead({ name: input.name, propertiesToReturn: propertyKeys })
if (!lead) {
return {
lead: undefined,
}
}
return {
lead: _mapHsLeadToBpLead(lead),
}
}
export const createLead: bp.IntegrationProps['actions']['createLead'] = async ({ client, ctx, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ client, ctx, logger })
const lead = await hsClient.createLead({
name: input.name,
contactEmailOrId: input.contact,
properties: propertiesEntriesToRecord(input.properties ?? []),
})
return {
lead: _mapHsLeadToBpLead(lead),
}
}
export const getLead: bp.IntegrationProps['actions']['getLead'] = async ({ client, ctx, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ client, ctx, logger })
const propertyKeys = await _getLeadPropertyKeys(hsClient)
const lead = await hsClient.getLeadById({ leadId: input.leadId, propertiesToReturn: propertyKeys })
return {
lead: _mapHsLeadToBpLead(lead),
}
}
export const updateLead: bp.IntegrationProps['actions']['updateLead'] = async ({ client, ctx, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ client, ctx, logger })
const lead = await hsClient.updateLead({
leadId: input.leadId,
name: input.name,
properties: propertiesEntriesToRecord(input.properties ?? []),
})
return {
lead: {
..._mapHsLeadToBpLead(lead),
name: lead.properties.hs_lead_name ?? undefined,
},
}
}
export const deleteLead: bp.IntegrationProps['actions']['deleteLead'] = async ({ client, ctx, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ client, ctx, logger })
await hsClient.deleteLead({ leadId: input.leadId })
return {}
}
+25
View File
@@ -0,0 +1,25 @@
import { getAuthenticatedHubspotClient } from '../utils'
import * as bp from '.botpress'
export const getOwner: bp.IntegrationProps['actions']['getOwner'] = async ({ ctx, client, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ ctx, client, logger })
try {
const owner = await hsClient.getOwnerById({ ownerId: input.ownerId })
return {
owner: {
id: owner.id,
email: owner.email,
firstName: owner.firstName,
lastName: owner.lastName,
userId: owner.userId,
type: owner.type,
archived: owner.archived,
createdAt: owner.createdAt.toISOString(),
updatedAt: owner.updatedAt.toISOString(),
},
}
} catch (err: unknown) {
logger.forBot().debug(`Owner ${input.ownerId} could not be retrieved: ${err}`)
return { owner: undefined }
}
}
@@ -0,0 +1,40 @@
import { z } from '@botpress/sdk'
import { ticketSchema } from '../../definitions/actions/ticket'
import { getAuthenticatedHubspotClient, propertiesEntriesToRecord } from '../utils'
import * as bp from '.botpress'
type HubspotClient = Awaited<ReturnType<typeof getAuthenticatedHubspotClient>>
type HsTicket = Awaited<ReturnType<HubspotClient['createTicket']>>
type BpTicket = z.infer<typeof ticketSchema>
const _mapHsTicketToBpTicket = (hsTicket: HsTicket): BpTicket => ({
id: hsTicket.id,
subject: hsTicket.properties.subject ?? '',
category: hsTicket.properties.hs_ticket_category ?? '',
description: hsTicket.properties.content ?? '',
priority: hsTicket.properties.hs_ticket_priority ?? '',
source: hsTicket.properties.source_type ?? '',
properties: hsTicket.properties,
})
export const createTicket: bp.IntegrationProps['actions']['createTicket'] = async ({ client, ctx, input, logger }) => {
const hsClient = await getAuthenticatedHubspotClient({ client, ctx, logger })
const newTicket = await hsClient.createTicket({
subject: input.subject,
category: input.category,
source: input.source,
description: input.description,
additionalProperties: propertiesEntriesToRecord(input.properties ?? []),
pipelineNameOrId: input.pipeline,
pipelineStageNameOrId: input.pipelineStage,
priority: input.priority,
ticketOwnerEmailOrId: input.ticketOwner,
requesterEmailOrId: input.requester,
companyIdOrNameOrDomain: input.company,
})
return {
ticket: _mapHsTicketToBpTicket(newTicket),
}
}