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,91 @@
import { z } from '@botpress/sdk'
export const getEventTypesInputSchema = z.object({
username: z.string().describe('The username of the Cal.com account').title('Username'),
})
export const getEventTypesOutputSchema = z.object({
eventTypes: z
.array(
z.object({
id: z.number().describe('The unique identifier of the event type').title('Event Type ID'),
lengthInMinutes: z.number().describe('The duration of the event type in minutes').title('Duration (minutes)'),
title: z.string().describe('The title of the event type').title('Title'),
slug: z.string().describe('The slug of the event type').title('Slug'),
description: z.string().describe('A brief description of the event type').title('Description'),
lengthInMinutesOptions: z
.array(z.number())
.describe('Available duration options for the event type')
.title('Duration Options (minutes)'),
})
)
.describe('A list of event types associated with the Cal.com account')
.title('Event Types'),
})
export const getAvailableTimeSlotsInputSchema = z.object({
eventTypeId: z.number().describe('The ID of the event type').title('Event Type ID'),
startDate: z
.string()
.optional()
.describe('Start date in YYYY-MM-DD format')
.describe('Start date in YYYY-MM-DD format')
.title('Start Date'),
endDate: z
.string()
.optional()
.describe('End date in YYYY-MM-DD format')
.describe('End date in YYYY-MM-DD format')
.title('End Date'),
})
export const getAvailableTimeSlotsOutputSchema = z.object({
slots: z
.record(z.string(), z.array(z.record(z.string(), z.string())))
.describe('Available time slots grouped by date')
.title('Available Time Slots'),
})
export const generateLinkInputSchema = z.object({
conversationId: z
.string()
.describe('The ID of the conversation')
.placeholder('{{ event.conversationId }}')
.title('Conversation ID'),
email: z
.string()
.email('Invalid email address')
.describe('The email of the user to send the link to')
.title('User Email'),
eventTypeId: z.number().describe('The ID of the event type').title('Event Type ID'),
})
export const generateLinkOutputSchema = z.object({
url: z.string().describe('The generated scheduling link').title('Scheduling Link URL'),
})
export const bookEventInputSchema = z.object({
eventTypeId: z.number().describe('The ID of the event type').title('Event Type ID'),
start: z
.string()
.describe('Start time in ISO 8601 format')
.describe('Start time in ISO 8601 format')
.title('Start Time'),
email: z
.string()
.email('Invalid email address')
.describe('The email of the user booking the event')
.title('User Email'),
name: z.string().describe('The name of the user booking the event').title('User Name'),
timeZone: z.string().describe('Time zone in IANA format, e.g., America/New_York').title('Time Zone'),
conversationId: z
.string()
.optional()
.placeholder('{{ event.conversationId }}')
.describe('The ID of the conversation')
.title('Conversation ID'),
})
export const bookEventOutputSchema = z.object({
success: z.boolean().describe('Indicates if the booking was successful').title('Success'),
})
+15
View File
@@ -0,0 +1,15 @@
import { z } from '@botpress/sdk'
export const calcomEventTypeShema = z.object({
id: z.number().describe('The unique identifier of the event type').title('Event Type ID'),
lengthInMinutes: z.number().describe('The duration of the event type in minutes').title('Duration (minutes)'),
title: z.string().describe('The title of the event type').title('Title'),
slug: z.string().describe('The slug of the event type').title('Slug'),
description: z.string().describe('A brief description of the event type').title('Description'),
lengthInMinutesOptions: z
.array(z.number())
.optional()
.describe('Available duration options for the event type')
.title('Duration Options (minutes)'),
hidden: z.boolean().describe('Indicates if the event type is hidden').title('Hidden'),
})
+13
View File
@@ -0,0 +1,13 @@
import rootConfig from '../../eslint.config.mjs'
export default [
...rootConfig,
{
languageOptions: {
parserOptions: {
project: ['./tsconfig.json'],
tsconfigRootDir: import.meta.dirname,
},
},
},
]
+1
View File
@@ -0,0 +1 @@
This integration allows you to interact with the Cal.com scheduling platform. It provides actions to list all available events for a sepecific user and to book an event on behalf of a user.
+17
View File
@@ -0,0 +1,17 @@
<svg width="112" height="112" viewBox="0 0 112 112" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_2899)">
<path d="M15.5582 65.817C9.82115 65.817 5.5 61.2763 5.5 55.6704C5.5 50.0459 9.6005 45.4678 15.5582 45.4678C18.7209 45.4678 20.909 46.4395 22.6191 48.6631L19.8609 50.9615C18.7025 49.7282 17.305 49.1116 15.5582 49.1116C11.6783 49.1116 9.54533 52.0827 9.54533 55.6704C9.54533 59.2582 11.8806 62.1732 15.5582 62.1732C17.2866 62.1732 18.7577 61.5566 19.9161 60.3233L22.6375 62.7151C21.001 64.8453 18.7577 65.817 15.5582 65.817Z" fill="#292929"/>
<path d="M34.5161 50.886H38.2304V65.4612H34.5161V63.331C33.7438 64.8446 32.4566 65.8536 29.9927 65.8536C26.0577 65.8536 22.9133 62.4341 22.9133 58.2297C22.9133 54.0253 26.0577 50.6057 29.9927 50.6057C32.4383 50.6057 33.7438 51.6148 34.5161 53.1283V50.886ZM34.6264 58.2297C34.6264 55.95 33.0634 54.0627 30.5995 54.0627C28.2274 54.0627 26.6828 55.9686 26.6828 58.2297C26.6828 60.4346 28.2274 62.3967 30.5995 62.3967C33.0451 62.3967 34.6264 60.4907 34.6264 58.2297Z" fill="#292929"/>
<path d="M40.8599 45H44.5742V65.4427H40.8599V45Z" fill="#292929"/>
<path d="M46.2291 63.5182C46.2291 62.3223 47.1853 61.3132 48.4908 61.3132C49.7964 61.3132 50.7158 62.3223 50.7158 63.5182C50.7158 64.7515 49.778 65.7605 48.4908 65.7605C47.2037 65.7605 46.2291 64.7515 46.2291 63.5182Z" fill="#292929"/>
<path d="M64.9296 63.1068C63.5505 64.7885 61.4543 65.8536 58.9719 65.8536C54.5404 65.8536 51.2858 62.4341 51.2858 58.2297C51.2858 54.0253 54.5404 50.6057 58.9719 50.6057C61.3623 50.6057 63.4402 51.6148 64.8193 53.2031L61.9508 55.6136C61.2336 54.7167 60.2958 54.044 58.9719 54.044C56.5999 54.044 55.0553 55.95 55.0553 58.211C55.0553 60.472 56.5999 62.378 58.9719 62.378C60.4062 62.378 61.3991 61.6306 62.1346 60.6215L64.9296 63.1068Z" fill="#292929"/>
<path d="M65.2422 58.2297C65.2422 54.0253 68.4968 50.6057 72.9283 50.6057C77.3598 50.6057 80.6144 54.0253 80.6144 58.2297C80.6144 62.4341 77.3598 65.8536 72.9283 65.8536C68.4968 65.8349 65.2422 62.4341 65.2422 58.2297ZM76.8449 58.2297C76.8449 55.95 75.3003 54.0627 72.9283 54.0627C70.5563 54.044 69.0117 55.95 69.0117 58.2297C69.0117 60.4907 70.5563 62.3967 72.9283 62.3967C75.3003 62.3967 76.8449 60.4907 76.8449 58.2297Z" fill="#292929"/>
<path d="M105.732 56.5482V65.4428H102.018V57.4638C102.018 54.9412 100.841 53.8574 99.076 53.8574C97.421 53.8574 96.2442 54.6796 96.2442 57.4638V65.4428H92.5299V57.4638C92.5299 54.9412 91.3346 53.8574 89.5878 53.8574C87.9329 53.8574 86.4802 54.6796 86.4802 57.4638V65.4428H82.7659V50.8676H86.4802V52.8857C87.2525 51.3161 88.65 50.5312 90.8014 50.5312C92.8425 50.5312 94.5525 51.5403 95.4903 53.2407C96.4281 51.5029 97.8072 50.5312 100.308 50.5312C103.36 50.5499 105.732 52.867 105.732 56.5482Z" fill="#292929"/>
</g>
<defs>
<clipPath id="clip0_1_2899">
<rect width="101" height="22" fill="white" transform="translate(5.5 45)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1,68 @@
import { z, IntegrationDefinition } from '@botpress/sdk'
import {
bookEventInputSchema,
bookEventOutputSchema,
generateLinkInputSchema,
generateLinkOutputSchema,
getAvailableTimeSlotsInputSchema,
getAvailableTimeSlotsOutputSchema,
getEventTypesInputSchema,
getEventTypesOutputSchema,
} from './definitions/actions'
export default new IntegrationDefinition({
name: 'calcom',
title: 'Cal.com',
version: '0.4.2',
readme: 'hub.md',
icon: 'icon.svg',
description: 'Schedule meetings and manage events using the Cal.com scheduling platform.',
configuration: {
schema: z.object({
calcomApiKey: z
.string()
.describe('Your Cal.com API Key. You can find it in your Cal.com account settings.')
.title('Cal.com API Key'),
}),
},
user: {
tags: {
id: { title: 'User ID', description: 'The ID of the user' },
},
},
events: {},
actions: {
getEventTypes: {
title: 'Get Event Types',
description: 'Fetches all event types from Cal.com',
input: { schema: getEventTypesInputSchema },
output: { schema: getEventTypesOutputSchema },
},
getAvailableTimeSlots: {
title: 'Get Available Time Slots',
description:
'Fetches available time slots for a specific event type within a date range ( default to next 7 days if not provided )',
input: { schema: getAvailableTimeSlotsInputSchema },
output: { schema: getAvailableTimeSlotsOutputSchema },
},
generateLink: {
title: 'Generate a link',
description: 'Generates a link to a calendar',
input: { schema: generateLinkInputSchema },
output: { schema: generateLinkOutputSchema },
},
bookEvent: {
title: 'Book an Event',
description: 'Books an event for a user',
input: { schema: bookEventInputSchema },
output: { schema: bookEventOutputSchema },
},
},
__advanced: {
useLegacyZuiTransformer: true,
},
attributes: {
category: 'Business Operations',
repo: 'botpress',
},
})
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@botpresshub/calcom",
"scripts": {
"build": "bp add -y && bp build",
"check:type": "tsc --noEmit",
"check:bplint": "bp lint",
"test": "vitest --run"
},
"private": true,
"dependencies": {
"@botpress/client": "workspace:*",
"@botpress/sdk": "workspace:*",
"axios": "^1.11.0"
},
"devDependencies": {
"@botpress/cli": "workspace:*",
"@botpress/sdk": "workspace:*"
}
}
@@ -0,0 +1,19 @@
import { ActionHandlerProps } from '@botpress/sdk/dist/integration'
import { CalcomApi } from 'src/calcom.api'
import { TIntegration } from '.botpress'
import { Input } from '.botpress/implementation/typings/actions/bookEvent/input'
export async function bookEvent(props: ActionHandlerProps<TIntegration, 'bookEvent', Input>) {
const { input, logger, ctx } = props
const calcom = new CalcomApi(ctx.configuration.calcomApiKey, logger.forBot())
const { start, email, name, eventTypeId, timeZone } = input
if (!email || !name || !eventTypeId) {
throw new Error('Email, Name and Event Type ID are required to book an event.')
}
const success = await calcom.bookEvent(eventTypeId, start, email, name, timeZone)
return { success }
}
@@ -0,0 +1,17 @@
import { ActionHandlerProps } from '@botpress/sdk/dist/integration'
import { CalcomApi } from 'src/calcom.api'
import { TIntegration } from '.botpress'
import { Input } from '.botpress/implementation/typings/actions/generateLink/input'
export async function generateLink(
props: ActionHandlerProps<TIntegration, 'generateLink', Input>
): Promise<{ url: string }> {
const { input, ctx } = props
const calcom = new CalcomApi(ctx.configuration.calcomApiKey, props.logger.forBot())
const url = await calcom.generateLink(input.email, input.eventTypeId)
return {
url,
}
}
@@ -0,0 +1,17 @@
import { ActionHandlerProps } from '@botpress/sdk/dist/integration'
import { CalcomApi } from 'src/calcom.api'
import { TIntegration } from '.botpress'
import { Input } from '.botpress/implementation/typings/actions/getAvailableTimeSlots/input'
export async function getAvailableTimeSlots(props: ActionHandlerProps<TIntegration, 'getAvailableTimeSlots', Input>) {
const { ctx, logger, input } = props
const calcom = new CalcomApi(ctx.configuration.calcomApiKey, logger.forBot())
const startDate = input.startDate ? new Date(input.startDate) : new Date()
const endDate = input.endDate ? new Date(input.endDate) : new Date(startDate.getTime() + 7 * 24 * 60 * 60 * 1000) // Default to next 7 days
const timeSlots = await calcom.getAvailableTimeSlots(input.eventTypeId, startDate, endDate)
return {
slots: timeSlots || {},
}
}
@@ -0,0 +1,22 @@
import { ActionHandlerProps } from '@botpress/sdk/dist/integration'
import { CalcomApi } from 'src/calcom.api'
import { TIntegration } from '.botpress'
import { Input } from '.botpress/implementation/typings/actions/getEventTypes/input'
export async function getEventTypes(props: ActionHandlerProps<TIntegration, 'getEventTypes', Input>) {
const { ctx, logger, input } = props
const calcom = new CalcomApi(ctx.configuration.calcomApiKey, logger.forBot())
const eventTypes = await calcom.getAllEventTypes(input.username)
return {
eventTypes: eventTypes.map((et) => ({
id: et.id,
lengthInMinutes: et.lengthInMinutes,
title: et.title,
slug: et.slug,
description: et.description,
lengthInMinutesOptions: et.lengthInMinutesOptions ?? [],
})),
}
}
+133
View File
@@ -0,0 +1,133 @@
import { z, IntegrationLogger } from '@botpress/sdk'
import axios, { type AxiosInstance } from 'axios'
import { calcomEventTypeShema } from 'definitions/calcom'
export type CalcomEventType = z.infer<typeof calcomEventTypeShema>
const CALCOM_API_BASE_URL = 'https://api.cal.com/v2'
export class CalcomApi {
private _axios: AxiosInstance
public constructor(
apiKey: string,
private _logger: IntegrationLogger
) {
if (!apiKey || !apiKey.startsWith('cal_')) {
throw new Error('Invalid API Key format. It should start with "cal_".')
}
this._axios = axios.create({
baseURL: CALCOM_API_BASE_URL,
headers: {
Authorization: `Bearer ${apiKey}`,
},
})
}
public async generateLink(email: string, eventTypeId: number): Promise<string> {
const slug = (await this.getEventType(eventTypeId))?.slug
// date time string in 24hours
const now = new Date()
now.setHours(now.getHours() + 24)
const expirationTime = now.toISOString()
const resp = await this._axios
.post(`/event-types/${eventTypeId}/private-links`, {
expiresAt: expirationTime,
})
.catch((err) => {
this._logger.error('calcom::generateLink error', err.response?.data || err.message)
throw new Error('Failed to generate link. Please check the logs for more details.')
})
return `${resp.data?.data?.bookingUrl}/${slug}?email=${email}`
}
public async getEventType(eventTypeId: number): Promise<CalcomEventType | null> {
const resp = await this._axios.get(`/event-types/${eventTypeId}`)
if (resp?.data) {
const parsedResult = calcomEventTypeShema.safeParse(resp.data.data?.eventType)
if (!parsedResult.success) {
this._logger.error('calcom::getEventType parsing error', parsedResult.error)
throw new Error('Failed to parse event type. Please check the logs for more details.')
}
return parsedResult.data
}
return null
}
public async getAllEventTypes(username?: string): Promise<CalcomEventType[]> {
const resp = await this._axios
.get('/event-types', {
params: {
username,
},
headers: {
'cal-api-version': '2024-06-14',
},
})
.catch((err) => {
this._logger.error('calcom::getAllEventTypes error', err.response?.data || err.message)
throw new Error('Failed to fetch event types. Please check the logs for more details.')
})
const parseResult = z.array(calcomEventTypeShema).safeParse(resp?.data?.data || [])
if (!parseResult.success) {
this._logger.error('calcom::getAllEventTypes parsing error', parseResult.error)
throw new Error('Failed to parse event types. Please check the logs for more details.')
}
return parseResult.data
}
public async getAvailableTimeSlots(eventTypeId: number, startDate: Date, endDate: Date) {
const resp = await this._axios
.get('/slots', {
params: {
eventTypeId,
start: startDate.toISOString(),
end: endDate.toISOString(),
},
headers: {
'cal-api-version': '2024-09-04',
},
})
.catch((err) => {
this._logger.error('calcom::getAvailableTimeSlots error', err.response?.data || err.message)
throw new Error('Failed to fetch available time slots. Please check the logs for more details.')
})
return resp?.data?.data || []
}
public async bookEvent(eventTypeId: number, startTime: string, email: string, name: string, timeZone: string) {
const resp = await this._axios
.post(
'/bookings',
{
eventTypeId,
start: startTime,
attendee: {
email,
name,
timeZone,
},
},
{
headers: {
'cal-api-version': '2024-08-13',
},
}
)
.catch((err) => {
this._logger.error('calcom::bookEvent error', JSON.stringify(err))
throw new Error('Failed to book event. Please check the logs for more details.')
})
return resp?.data?.status === 'success'
}
}
+24
View File
@@ -0,0 +1,24 @@
import * as sdk from '@botpress/sdk'
import { bookEvent } from './actions/bookEvent'
import { generateLink } from './actions/generateLink'
import { getAvailableTimeSlots } from './actions/getAvailableTimeSlots'
import { getEventTypes } from './actions/getEventTypes'
import * as bp from '.botpress'
export default new bp.Integration({
register: async (props) => {
const calcomApiKey = props.ctx.configuration.calcomApiKey
if (!calcomApiKey || !calcomApiKey.startsWith('cal_')) {
throw new sdk.RuntimeError('The Cal.com API key is not configured. Please set it up in the configuration.')
}
},
unregister: async () => {},
channels: {},
handler: async () => {},
actions: {
generateLink,
getEventTypes,
getAvailableTimeSlots,
bookEvent,
},
})
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"paths": { "*": ["./*"] },
"outDir": "dist"
},
"include": [".botpress/**/*", "definitions/**/*", "src/**/*", "*.ts"]
}
+2
View File
@@ -0,0 +1,2 @@
import config from '../../vitest.config'
export default config