使用AI和NextCloud/Google/Zoho将事件文本转换为日历条目
高级
这是一个Content Creation, Multimodal AI领域的自动化工作流,包含 19 个节点。主要使用 If, Switch, Webhook, HttpRequest, GoogleCalendar 等节点。 通过AI和NextCloud/Google/Zoho将事件文本转换为日历条目
前置要求
- •HTTP Webhook 端点(n8n 会自动生成)
- •可能需要目标 API 的认证凭证
- •OpenAI API Key
使用的节点 (19)
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"meta": {
"instanceId": "90f9a6ef38ec632934192a5de51518245cd649d4287258dedc9971969910cdb7"
},
"nodes": [
{
"id": "758f90ce-d94a-4c62-a793-7ddd088178f9",
"name": "to_UTC",
"type": "@n8n/n8n-nodes-langchain.toolCode",
"position": [
1120,
560
],
"parameters": {
"name": "to_UTC",
"jsCode": "function toICSDate(query) {\n let d;\n\n if (query instanceof Date) {\n d = query;\n\n } else if (typeof query === 'string') {\n if (/^\\d{8}T\\d{6}Z$/.test(query)) {\n // Already ICS UTC format\n return query;\n } else if (/^\\d{8}T\\d{6}$/.test(query)) {\n // Compact local format, parse manually\n const [datePart, timePart] = query.split('T');\n const year = +datePart.slice(0, 4);\n const month = +datePart.slice(4, 6) - 1; // 0‑based\n const day = +datePart.slice(6, 8);\n const hour = +timePart.slice(0, 2);\n const minute = +timePart.slice(2, 4);\n const second = +timePart.slice(4, 6);\n d = new Date(year, month, day, hour, minute, second);\n } else {\n // Assume ISO‑8601 string (\"2025-09-26T11:00:00-04:00\")\n d = new Date(query);\n }\n }\n\n if (!(d instanceof Date) || isNaN(d)) {\n throw new Error('Invalid input: must be Date, ISO, ICS UTC, or compact string');\n }\n\n // Always format UTC\n return d.toISOString()\n .replace(/[-:]/g, '') // strip separators\n .replace(/\\.\\d{3}Z$/, 'Z'); // remove ms\n}\n\n// Export\nreturn toICSDate(query);",
"description": "=Call this tool and provide a datetime object in *{{ $now.zone.zoneName }}* timezone and this will give you the UTC time, in string format \"yyyyMMdd'T'HHmmss'Z'\"."
},
"typeVersion": 1.1
},
{
"id": "6917181e-dfdc-4cec-b621-8ce68c774231",
"name": "Brain",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1020,
560
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "bbcd8973-12d9-4d85-b6f6-4cec224ad447",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
480,
240
],
"parameters": {
"color": 4,
"height": 663.3731744745251,
"content": "## START\nSend a text block of event data (like an from an email body or text extracted from an image) to property **`eventInfo`**."
},
"typeVersion": 1
},
{
"id": "8b7d5184-0f36-4ae8-87d1-bbaf4b8133f0",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
740,
240
],
"parameters": {
"color": 7,
"height": 663.3731744745248,
"content": "## EXPANSION\n(Optional) If you wanted this workflow to receive an image, and send that directly to the language model to parse for event data, you can use this switch node to send the binary image down a different pathway.\n\nThat's up to you to build ;-)"
},
"typeVersion": 1
},
{
"id": "2c338370-8748-4c8f-8bdd-29f8b25f86fb",
"name": "Switch",
"type": "n8n-nodes-base.switch",
"disabled": true,
"position": [
780,
720
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Text",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "19b65514-2493-4128-8e9c-c1dd40454ac6",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.body.eventInfo }}",
"rightValue": ""
}
]
},
"renameOutput": true
},
{
"outputKey": "Image",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "object",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $binary }}",
"rightValue": ""
}
]
},
"renameOutput": true
}
]
},
"options": {
"allMatchingOutputs": false
}
},
"typeVersion": 3.1
},
{
"id": "2f211450-6b40-4bac-8a1b-00592164d11d",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1000,
240
],
"parameters": {
"width": 358.7342715692612,
"height": 663.3731744745251,
"content": "## PARSE EVENT DETAILS\nThis agent takes in a block of unformatted text that contains info about an event (eg. time, place, event name...) and parses it to a structured output."
},
"typeVersion": 1
},
{
"id": "276e4820-bbab-48a2-9d9d-dd4275924052",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1380,
240
],
"parameters": {
"color": 5,
"height": 663.3731744745251,
"content": "## CREATE EVENT\nSends a request to `NextCloud` CalDAV to create an event in your calendar.\n\nYou'll need to add `Basic Auth` credentials (user/pass) from your NextCloud account.\n\nCreate an app-specific password here: [your.NC.url/index.php/settings/user/security]"
},
"typeVersion": 1
},
{
"id": "93d6676b-f337-4052-927a-d4a86e9d589d",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1640,
240
],
"parameters": {
"color": 3,
"height": 663.3731744745251,
"content": "## RESPOND\nSend your success response back to the source system. You can edit this to return a JSON object instead if required.\n\n**PS** - This workflow plugs in seamlessly with [this iCloud Shortcut](https://www.icloud.com/shortcuts/8a107ea08ec4471d877b019520a4802c)"
},
"typeVersion": 1
},
{
"id": "6b37e5dd-e26a-4920-a929-9a9749ad8cc1",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
480,
920
],
"parameters": {
"color": 7,
"width": 1400.3576539723263,
"height": 258.08325199688073,
"content": "## SUGGESTIONS\nIn production setting, you'll want to account for errors and bad input. What if the user just puts in random text with no event details? The Ai should be instructed to then output a specific fail response that goes back to the source system to tell it the input was no good.\n\nIf your NextCloud is self-hosted, and there's a chance it could be down, you may want to catch an error there as well, and announce that back to the source system, via webhook response.\n\nI use a version of this workflow with an iOS shortcut for my iPhone. I tell Siri \"Add this to my calendar\" and it opens the camera, I snap, and it sends the text in the photo to this workflow. Very easy to use. *If you have an iPhone*, here's the Shortcut for you to do the same:\n\n#### ⭐ https://www.icloud.com/shortcuts/8a107ea08ec4471d877b019520a4802c"
},
"typeVersion": 1
},
{
"id": "4e383a02-ece9-4c0f-a8a6-4090885e6c40",
"name": "Create Zoho Event (API)",
"type": "n8n-nodes-base.httpRequest",
"notes": "Documentation:\nhttps://www.zoho.com/calendar/help/api/post-create-event.html",
"disabled": true,
"position": [
1420,
720
],
"parameters": {
"url": "https://calendar.zoho.com/api/v1/calendars/INSERT_CAL_UID_HERE/events",
"method": "POST",
"options": {},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "oAuth2Api",
"queryParameters": {
"parameters": [
{
"name": "eventdata",
"value": "={\n \"dateandtime\": {\n \"timezone\": {{ JSON.stringify($now.zone.zoneName) }},\n \"start\": {{ JSON.stringify($json.output.startTime) }},\n \"end\": {{ JSON.stringify($json.output.endTime) }}\n },\n \"title\": {{ JSON.stringify($json.output.eventTitle) }},\n \"location\": {{ JSON.stringify($json.output.location != null ? $json.output.location : \"\") }},\n \"url\": {{ JSON.stringify($json.output.url != null ? $json.output.url : \"\") }},\n \"transparency\": 1,\n \"attendees\": [\n {\n \"email\": \"INSERT_EMAIL_HERE\"\n }\n ],\n \"description\": {{ JSON.stringify($json.output.description != null ? $json.output.description : \"\") }}\n}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "06883e54-7548-448d-92d8-6d7079bdbf43",
"name": "Structured Output",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
1220,
560
],
"parameters": {
"jsonSchemaExample": "{\n \"eventTitle\": \"Marketing Conference\",\n \"description\": null,\n \"startTime\": \"20250912T160000Z\",\n \"endTime\": \"20250912T200000Z\",\n \"location\": \"15000 Park Ave, Suite 1234, New York, New York, United States\",\n \"url\": null\n}"
},
"typeVersion": 1.2
},
{
"id": "1102b5a1-932b-4ab9-a2d3-17950c4b8b99",
"name": "Success Response",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1700,
540
],
"parameters": {
"options": {
"responseCode": 201
},
"respondWith": "text",
"responseBody": "=Your calendar event was successfully created."
},
"typeVersion": 1.1
},
{
"id": "c162405c-ed1a-4177-8f9f-5110ffbeb426",
"name": "Fail Response",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1700,
740
],
"parameters": {
"options": {
"responseCode": 400
},
"respondWith": "text",
"responseBody": "=There was a problem with the event info. Try again."
},
"typeVersion": 1.1
},
{
"id": "7f11b9f4-526e-45fd-8e89-e89c8b9e2db4",
"name": "NextCloud Cal Event Creation",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
1440,
540
],
"parameters": {
"url": "https://your.nextcloudurl.com/remote.php/dav/calendars/YOUR_USER/personal/newEvent.ics",
"body": "=BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//n8n//CalDAV Connector//EN\nBEGIN:VEVENT\nUID:123456789@example.com\nDTSTAMP:{{ $now.setZone($now.zone.zoneName).toUTC().toFormat(\"yyyyMMdd'T'HHmmss'Z'\") }}\nDTSTART:{{ $json.output.startTime }}\nDTEND:{{ $json.output.endTime }}\nSUMMARY:{{ $json.output.eventTitle }}\nDESCRIPTION:{{ $json.output.description }}\nLOCATION:{{ $json.output.location }}\nURL:{{ $json.output.url }}\nEND:VEVENT\nEND:VCALENDAR",
"method": "PUT",
"options": {},
"sendBody": true,
"contentType": "raw",
"rawContentType": "text/calendar; charset=utf-8"
},
"typeVersion": 4.2
},
{
"id": "9a789e02-203a-40b8-a834-aa6fa6881ce7",
"name": "Good Parse",
"type": "n8n-nodes-base.if",
"position": [
1040,
760
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "3bd94ff5-bebb-43a4-ae06-7790de6df7a6",
"operator": {
"type": "string",
"operation": "notExists",
"singleValue": true
},
"leftValue": "={{ $json.output.error }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.1
},
{
"id": "c6bb115d-bf79-457d-a8db-fb4a8ef1bcf5",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
480,
-260
],
"parameters": {
"color": 5,
"width": 435.7280989463619,
"height": 478.57603298212047,
"content": "## Eric Knaus www.MarketingGuy.ai\nFind me on LinkedIn: https://linkedin.com/in/ericknaus\n\n\n\n## TTC - Text-to-Calendar event"
},
"typeVersion": 1
},
{
"id": "f10d8f8d-88b0-49be-8263-14c7a343bc1d",
"name": "Google Calendar",
"type": "n8n-nodes-base.googleCalendar",
"disabled": true,
"position": [
1480,
740
],
"parameters": {
"end": "={{ $json.output.endTime }}",
"start": "={{ $json.output.startTime }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": ""
},
"additionalFields": {}
},
"typeVersion": 1.1
},
{
"id": "b4be10a7-0306-425e-9e24-f449dc198ae0",
"name": "Inbound Event Info",
"type": "n8n-nodes-base.webhook",
"position": [
540,
540
],
"webhookId": "2ee09e2c-6ee0-4e12-a941-a40a63442bb1",
"parameters": {
"path": "make-cal-event-xdt8gh4-rf3827",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "80fc0363-4991-4eb9-a019-17b8597e9bfe",
"name": "Parse Event Info",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1040,
380
],
"parameters": {
"text": "=Local time context: {{ $now.toString() }} \nServer timezone: {{ $now.zone.zoneName }} \n\nInstructions:\n1. Extract calendar event details from the text below. \n2. For event times, pass each local datetime through `to_UTC(Datetime)` to get UTC output values, in \"YYYYMMDDTHHmmssZ\" format. \n4. Apply special cases:\n - Case 1: If text has a date and at least one person/place but no clear details → infer event. If no time, use 08:00–22:00 local. \n - Case 2: If there is no date and no person/place/event → output exactly: {\"error\": \"BAD INPUT\"} \n\nText to parse:\n{{ JSON.stringify($json.body.eventInfo) }}",
"options": {
"systemMessage": "You are an assistant that extracts calendar event information from text.\n\nAlways respond ONLY in JSON.\n\nNormal Output Schema (when event is valid or inferable):\n{\n \"eventTitle\": \"string\",\n \"description\": \"string or null\",\n \"startTime\": \"YYYYMMDDTHHmmssZ\", // UTC, ISO8601\n \"endTime\": \"YYYYMMDDTHHmmssZ\", // UTC, ISO8601\n \"location\": \"string or null\",\n \"url\": \"string or null\"\n}\n\nSpecial Rules:\n1. If text includes at least one date and a name or place but lacks other details → infer a plausible event. \n - If no time is provided → assume 08:00 for start time and 22:00 for end time, local time. Convert using `to_UTC`. \n2. If text has no date and no usable event/person/place detail → output:\n {\n \"error\": \"BAD INPUT\"\n }\n\nFormatting Rules:\n- Output strictly valid JSON only.\n- Use null values for missing optional fields unless you are inferring details under rule (1).\n- Use the `to_UTC(Datetime)` tool to convert each local datetime to UTC."
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.6
}
],
"pinData": {},
"connections": {
"6917181e-dfdc-4cec-b621-8ce68c774231": {
"ai_languageModel": [
[
{
"node": "80fc0363-4991-4eb9-a019-17b8597e9bfe",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"758f90ce-d94a-4c62-a793-7ddd088178f9": {
"ai_tool": [
[
{
"node": "80fc0363-4991-4eb9-a019-17b8597e9bfe",
"type": "ai_tool",
"index": 0
}
]
]
},
"9a789e02-203a-40b8-a834-aa6fa6881ce7": {
"main": [
[
{
"node": "7f11b9f4-526e-45fd-8e89-e89c8b9e2db4",
"type": "main",
"index": 0
}
],
[
{
"node": "c162405c-ed1a-4177-8f9f-5110ffbeb426",
"type": "main",
"index": 0
}
]
]
},
"80fc0363-4991-4eb9-a019-17b8597e9bfe": {
"main": [
[
{
"node": "9a789e02-203a-40b8-a834-aa6fa6881ce7",
"type": "main",
"index": 0
}
]
]
},
"06883e54-7548-448d-92d8-6d7079bdbf43": {
"ai_outputParser": [
[
{
"node": "80fc0363-4991-4eb9-a019-17b8597e9bfe",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"b4be10a7-0306-425e-9e24-f449dc198ae0": {
"main": [
[
{
"node": "80fc0363-4991-4eb9-a019-17b8597e9bfe",
"type": "main",
"index": 0
}
]
]
},
"7f11b9f4-526e-45fd-8e89-e89c8b9e2db4": {
"main": [
[
{
"node": "1102b5a1-932b-4ab9-a2d3-17950c4b8b99",
"type": "main",
"index": 0
}
],
[
{
"node": "c162405c-ed1a-4177-8f9f-5110ffbeb426",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 内容创作, 多模态 AI
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
使用 OpenAI、LangChain 和 API 集成的工作流自动化初学者指南
使用 OpenAI、LangChain 和 API 集成的工作流自动化初学者指南
If
Set
Code
+
If
Set
Code
33 节点Meelioo
内容创作
使用 GPT-4o、Fal.ai 和人工监督生成产品 AI 宣传视频
使用 GPT-4o、Fal.ai 和人工监督生成产品 AI 宣传视频
If
Set
Code
+
If
Set
Code
72 节点gotoHuman
内容创作
使用GPT-4和DALL-E在UI基础上实现自动化LinkedIn内容创建
基于AI的LinkedIn内容生成器(OpenAI GPT-4和DALL-E)
Webhook
Http Request
Agent
+
Webhook
Http Request
Agent
23 节点WeWeb
内容创作
从趋势电子表格生成SEO内容到存储(SharePoint/Drive/Dropbox)
使用GPT-4o、FAL AI和多存储支持从趋势自动生成SEO内容
If
Set
Code
+
If
Set
Code
47 节点plemeo
内容创作
✅ 病毒式Reels工厂
使用Veo、Shotstack和Postiz自动化ASMR玻璃水果视频创作与发布
If
Jwt
Set
+
If
Jwt
Set
37 节点Ayoub Boutouil
内容创作
使用OpenAI、RunwayML和ElevenLabs自动化无脸短视频
使用OpenAI、RunwayML和ElevenLabs自动化无脸短视频:从脚本到社交媒体
Set
Code
Wait
+
Set
Code
Wait
56 节点LeeWei
内容创作
工作流信息
难度等级
高级
节点数量19
分类2
节点类型11
作者
Automation Wizard bzzt Years of experience as a digital marketer. Honed skill in JS, web dev, email mktg. All about the value to the client. Value, baby, it's everything.
外部链接
在 n8n.io 查看 →
分享此工作流