first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,18 @@
{
"node": "n8n-nodes-base.calTrigger",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Productivity", "Utility"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/credentials/cal/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.caltrigger/"
}
]
}
}
@@ -0,0 +1,268 @@
import {
type IDataObject,
type IHookFunctions,
type IWebhookFunctions,
type ILoadOptionsFunctions,
type INodePropertyOptions,
type INodeType,
type INodeTypeDescription,
type IWebhookResponseData,
NodeConnectionTypes,
} from 'n8n-workflow';
import { calApiRequest, sortOptionParameters } from './GenericFunctions';
export class CalTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Cal.com Trigger',
name: 'calTrigger',
icon: { light: 'file:cal.svg', dark: 'file:cal.dark.svg' },
group: ['trigger'],
version: [1, 2],
subtitle: '=Events: {{$parameter["events"].join(", ")}}',
description: 'Handle Cal.com events via webhooks',
defaults: {
name: 'Cal.com Trigger',
},
inputs: [],
outputs: [NodeConnectionTypes.Main],
credentials: [
{
name: 'calApi',
required: true,
},
],
webhooks: [
{
name: 'default',
httpMethod: 'POST',
responseMode: 'onReceived',
path: 'webhook',
},
],
properties: [
{
displayName: 'Events',
name: 'events',
type: 'multiOptions',
options: [
{
name: 'Booking Cancelled',
value: 'BOOKING_CANCELLED',
description: 'Receive notifications when a Cal event is canceled',
},
{
name: 'Booking Created',
value: 'BOOKING_CREATED',
description: 'Receive notifications when a new Cal event is created',
},
{
name: 'Booking Rescheduled',
value: 'BOOKING_RESCHEDULED',
description: 'Receive notifications when a Cal event is rescheduled',
},
{
name: 'Meeting Ended',
value: 'MEETING_ENDED',
description: 'Receive notifications when a Cal event or meeting has ended',
},
],
default: [],
required: true,
},
{
displayName: 'API Version',
name: 'version',
type: 'options',
displayOptions: {
show: {
'@version': [1],
},
},
isNodeSetting: true,
options: [
{
name: 'Before v2.0',
value: 1,
},
{
name: 'v2.0 Onwards',
value: 2,
},
],
default: 1,
},
{
displayName: 'API Version',
name: 'version',
type: 'options',
displayOptions: {
show: {
'@version': [2],
},
},
isNodeSetting: true,
options: [
{
name: 'Before v2.0',
value: 1,
},
{
name: 'v2.0 Onwards',
value: 2,
},
],
default: 2,
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Field',
default: {},
options: [
{
displayName: 'App ID',
name: 'appId',
type: 'string',
description: 'The ID of the App to monitor',
default: '',
},
{
displayName: 'EventType Name or ID',
name: 'eventTypeId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getEventTypes',
},
description:
'The EventType to monitor. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
default: '',
},
{
displayName: 'Payload Template',
name: 'payloadTemplate',
type: 'string',
description: 'Template to customize the webhook payload',
default: '',
typeOptions: {
rows: 4,
},
},
],
},
],
};
methods = {
loadOptions: {
async getEventTypes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const data = await calApiRequest.call(this, 'GET', '/event-types', {});
for (const item of data.event_types) {
returnData.push({
name: item.title,
value: item.id,
});
}
return sortOptionParameters(returnData);
},
},
};
webhookMethods = {
default: {
async checkExists(this: IHookFunctions): Promise<boolean> {
const version = this.getNodeParameter('version', 0) as number;
const webhookUrl = this.getNodeWebhookUrl('default');
const webhookData = this.getWorkflowStaticData('node');
const events = this.getNodeParameter('events') as string;
// Check all the webhooks which exist already if it is identical to the
// one that is supposed to get created.
const data =
version === 2
? await calApiRequest.call(this, 'GET', '/webhooks', {})
: await calApiRequest.call(this, 'GET', '/hooks', {});
for (const webhook of data.webhooks) {
if (webhook.subscriberUrl === webhookUrl) {
for (const event of events) {
if (!webhook.eventTriggers.includes(event)) {
return false;
}
}
// Set webhook-id to be sure that it can be deleted
webhookData.webhookId = webhook.id as string;
return true;
}
}
return false;
},
async create(this: IHookFunctions): Promise<boolean> {
const version = this.getNodeParameter('version', 0) as number;
const webhookData = this.getWorkflowStaticData('node');
const subscriberUrl = this.getNodeWebhookUrl('default');
const eventTriggers = this.getNodeParameter('events') as string;
const options = this.getNodeParameter('options');
const active = true;
const body = {
subscriberUrl,
eventTriggers,
active,
...(options as object),
};
const responseData =
version === 2
? await calApiRequest.call(this, 'POST', '/webhooks', body)
: await calApiRequest.call(this, 'POST', '/hooks', body);
if (responseData.webhook.id === undefined) {
// Required data is missing so was not successful
return false;
}
webhookData.webhookId = responseData.webhook.id as string;
return true;
},
async delete(this: IHookFunctions): Promise<boolean> {
const version = this.getNodeParameter('version', 0) as number;
const webhookData = this.getWorkflowStaticData('node');
if (webhookData.webhookId !== undefined) {
const endpoint =
version === 2
? `/webhooks/${webhookData.webhookId}`
: `/hooks/${webhookData.webhookId}`;
try {
await calApiRequest.call(this, 'DELETE', endpoint);
} catch (error) {
return false;
}
// Remove from the static workflow data so that it is clear
// that no webhooks are registered anymore
delete webhookData.webhookId;
}
return true;
},
},
};
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
const req = this.getRequestObject();
return {
workflowData: [
this.helpers.returnJsonArray({
triggerEvent: req.body.triggerEvent as string,
createdAt: req.body.createdAt as string,
...(req.body.payload as IDataObject),
}),
],
};
}
}
@@ -0,0 +1,60 @@
import type {
IDataObject,
IExecuteFunctions,
ILoadOptionsFunctions,
IHookFunctions,
IHttpRequestMethods,
IHttpRequestOptions,
INodePropertyOptions,
IWebhookFunctions,
JsonObject,
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
export async function calApiRequest(
this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
resource: string,
body: any = {},
query: IDataObject = {},
option: IDataObject = {},
): Promise<any> {
const credentials = await this.getCredentials('calApi');
let options: IHttpRequestOptions = {
baseURL: credentials.host as string,
method,
body,
qs: query,
url: resource,
};
if (!Object.keys(query).length) {
delete options.qs;
}
options = Object.assign({}, options, option);
try {
return await this.helpers.httpRequestWithAuthentication.call(this, 'calApi', options);
} catch (error) {
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}
export function sortOptionParameters(
optionParameters: INodePropertyOptions[],
): INodePropertyOptions[] {
optionParameters.sort((a, b) => {
const aName = a.name.toLowerCase();
const bName = b.name.toLowerCase();
if (aName < bName) {
return -1;
}
if (aName > bName) {
return 1;
}
return 0;
});
return optionParameters;
}
@@ -0,0 +1,9 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.5935 16H14.1114V24.1582H15.5935V16Z" fill="#FAFAFA"/>
<path d="M4.01413 24.3075C1.72452 24.3075 0 22.4954 0 20.2583C0 18.0137 1.63644 16.1867 4.01413 16.1867C5.27604 16.1867 6.14941 16.5744 6.83181 17.4618L5.73124 18.3791C5.26864 17.8869 4.71095 17.6408 4.01413 17.6408C2.46577 17.6408 1.61422 18.8265 1.61422 20.2583C1.61422 21.6901 2.54644 22.8534 4.01413 22.8534C4.70354 22.8534 5.29085 22.6073 5.75306 22.1151L6.83922 23.0696C6.18604 23.9198 5.29085 24.3075 4.01413 24.3075Z" fill="#FAFAFA"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5794 18.3489H13.0619V24.1655H11.5794V23.3155C11.2711 23.9195 10.7575 24.3222 9.77421 24.3222C8.20402 24.3222 6.94912 22.9575 6.94912 21.2796C6.94912 19.6018 8.20402 18.2371 9.77421 18.2371C10.7505 18.2371 11.2711 18.6398 11.5794 19.2438V18.3489ZM11.6234 21.2796C11.6234 20.3698 10.9999 19.6167 10.0166 19.6167C9.06998 19.6167 8.45344 20.3773 8.45344 21.2796C8.45344 22.1595 9.06998 22.9426 10.0166 22.9426C10.9925 22.9426 11.6234 22.182 11.6234 21.2796Z" fill="#FAFAFA"/>
<path d="M16.2541 23.3901C16.2541 22.9129 16.6356 22.5102 17.1567 22.5102C17.6777 22.5102 18.0445 22.9129 18.0445 23.3901C18.0445 23.8824 17.6703 24.285 17.1567 24.285C16.643 24.285 16.2541 23.8824 16.2541 23.3901Z" fill="#FAFAFA"/>
<path d="M21.3396 24.3222C22.3302 24.3222 23.1666 23.8971 23.7169 23.226L22.6015 22.2341C22.308 22.6369 21.9117 22.9351 21.3396 22.9351C20.3929 22.9351 19.7764 22.1745 19.7764 21.2722C19.7764 20.3698 20.3929 19.6092 21.3396 19.6092C21.8676 19.6092 22.2422 19.8777 22.5282 20.2356L23.6728 19.2737C23.1225 18.6398 22.2932 18.2371 21.3396 18.2371C19.571 18.2371 18.2721 19.6018 18.2721 21.2796C18.2721 22.9575 19.571 24.3222 21.3396 24.3222Z" fill="#FAFAFA"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8416 21.2796C23.8416 19.6018 25.1405 18.2371 26.9091 18.2371C28.6772 18.2371 29.9762 19.6018 29.9762 21.2796C29.9762 22.9575 28.6772 24.3222 26.9091 24.3222C25.1405 24.3147 23.8416 22.9575 23.8416 21.2796ZM28.4719 21.2796C28.4719 20.3698 27.8553 19.6167 26.9091 19.6167C25.9624 19.6092 25.3459 20.3698 25.3459 21.2796C25.3459 22.182 25.9624 22.9426 26.9091 22.9426C27.8553 22.9426 28.4719 22.182 28.4719 21.2796Z" fill="#FAFAFA"/>
<path d="M40 24.1582V20.6086C40 19.1395 39.0534 18.2148 37.8353 18.2074C36.8374 18.2074 36.2869 18.5952 35.9128 19.2887C35.5385 18.6101 34.8561 18.2074 34.0416 18.2074C33.183 18.2074 32.6251 18.5206 32.3168 19.147V18.3416H30.8347V24.1582H32.3168V20.974C32.3168 19.8629 32.8967 19.5348 33.5571 19.5348C34.2543 19.5348 34.7314 19.9673 34.7314 20.974V24.1582H36.2137V20.974C36.2137 19.8629 36.6833 19.5348 37.3437 19.5348C38.0481 19.5348 38.5177 19.9673 38.5177 20.974V24.1582H40Z" fill="#FAFAFA"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.5936 16H14.1115V24.1582H15.5936V16Z" fill="#292929"/>
<path d="M4.01415 24.3076C1.72452 24.3076 0 22.4955 0 20.2583C0 18.0137 1.63645 16.1867 4.01415 16.1867C5.27607 16.1867 6.14944 16.5745 6.83184 17.4619L5.73127 18.3791C5.26867 17.8869 4.71097 17.6408 4.01415 17.6408C2.46578 17.6408 1.61423 18.8265 1.61423 20.2583C1.61423 21.6901 2.54645 22.8534 4.01415 22.8534C4.70357 22.8534 5.29088 22.6074 5.75309 22.1151L6.83925 23.0697C6.18607 23.9198 5.29088 24.3076 4.01415 24.3076Z" fill="#292929"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5794 18.349H13.062V24.1656H11.5794V23.3155C11.2712 23.9195 10.7575 24.3222 9.77425 24.3222C8.20406 24.3222 6.94915 22.9575 6.94915 21.2797C6.94915 19.6018 8.20406 18.2371 9.77425 18.2371C10.7505 18.2371 11.2712 18.6398 11.5794 19.2438V18.349ZM11.6235 21.2797C11.6235 20.3699 10.9999 19.6167 10.0167 19.6167C9.07002 19.6167 8.45348 20.3773 8.45348 21.2797C8.45348 22.1596 9.07002 22.9426 10.0167 22.9426C10.9925 22.9426 11.6235 22.182 11.6235 21.2797Z" fill="#292929"/>
<path d="M16.2542 23.3902C16.2542 22.913 16.6357 22.5102 17.1568 22.5102C17.6778 22.5102 18.0446 22.913 18.0446 23.3902C18.0446 23.8824 17.6704 24.285 17.1568 24.285C16.6431 24.285 16.2542 23.8824 16.2542 23.3902Z" fill="#292929"/>
<path d="M21.3397 24.3222C22.3303 24.3222 23.1667 23.8971 23.717 23.226L22.6016 22.2342C22.3081 22.6369 21.9118 22.9352 21.3397 22.9352C20.393 22.9352 19.7765 22.1745 19.7765 21.2722C19.7765 20.3699 20.393 19.6092 21.3397 19.6092C21.8677 19.6092 22.2423 19.8777 22.5283 20.2357L23.6729 19.2737C23.1227 18.6398 22.2933 18.2371 21.3397 18.2371C19.5711 18.2371 18.2722 19.6018 18.2722 21.2797C18.2722 22.9575 19.5711 24.3222 21.3397 24.3222Z" fill="#292929"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8417 21.2797C23.8417 19.6018 25.1406 18.2371 26.9092 18.2371C28.6774 18.2371 29.9763 19.6018 29.9763 21.2797C29.9763 22.9575 28.6774 24.3222 26.9092 24.3222C25.1406 24.3147 23.8417 22.9575 23.8417 21.2797ZM28.472 21.2797C28.472 20.3699 27.8555 19.6167 26.9092 19.6167C25.9626 19.6092 25.346 20.3699 25.346 21.2797C25.346 22.182 25.9626 22.9426 26.9092 22.9426C27.8555 22.9426 28.472 22.182 28.472 21.2797Z" fill="#292929"/>
<path d="M40 24.1582V20.6086C40 19.1396 39.0538 18.2148 37.8355 18.2074C36.8374 18.2074 36.2871 18.5952 35.913 19.2887C35.5384 18.6101 34.856 18.2074 34.0415 18.2074C33.183 18.2074 32.6253 18.5206 32.317 19.147V18.3416H30.8349V24.1582H32.317V20.974C32.317 19.8629 32.8969 19.5348 33.5575 19.5348C34.2543 19.5348 34.7313 19.9673 34.7313 20.974V24.1582H36.2138V20.974C36.2138 19.8629 36.6835 19.5348 37.344 19.5348C38.0483 19.5348 38.5179 19.9673 38.5179 20.974V24.1582H40Z" fill="#292929"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB