This workflow corresponds to n8n.io template #7228 — we link there as the canonical source.
This workflow follows the Agent → Google Sheets recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "HzZvyjb6riqHb2FR",
"name": "export n8n",
"tags": [],
"nodes": [
{
"id": "74a3253b-68d1-4745-85a1-70f5fa3260a9",
"name": "Switch",
"type": "n8n-nodes-base.switch",
"position": [
-592,
48
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "80b1a421-efcd-487e-8c18-925496da9b0c",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.events[0].message.type }}",
"rightValue": "text"
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "c124e4cd-78b0-4ce1-bf72-26806f7211e8",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.events[0].message.type }}",
"rightValue": "image"
}
]
}
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"typeVersion": 3.2
},
{
"id": "adf17e8b-4e45-4ad4-90e9-208b5bf6207f",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
160,
-160
],
"parameters": {
"text": "={{ $json.text }}",
"options": {
"systemMessage": "=You are an AI assistant. Your job is to respond to user messages."
},
"promptType": "define"
},
"typeVersion": 2.1
},
{
"id": "ef15eb25-64d9-4c30-becc-7f338d46ce55",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
32,
48
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "17e136f4-34f7-42c8-9793-19d13610aa49",
"name": "Analyze image",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
-96,
272
],
"parameters": {
"text": "=Estimate calories only if the item in this image is a food item, and output the calories in the following simple JSON format:\n\n[{\n\"dishName\": \"result\",\n\"calories\": \"result\",\n}]\n\nDo not output anything but this JSON only if it is food.",
"modelId": {
"__rl": true,
"mode": "list",
"value": "chatgpt-4o-latest",
"cachedResultName": "CHATGPT-4O-LATEST"
},
"options": {},
"resource": "image",
"inputType": "base64",
"operation": "analyze"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.8
},
{
"id": "0359b260-8639-475b-8d47-cc76e004f027",
"name": "Edit Fields1",
"type": "n8n-nodes-base.set",
"position": [
576,
272
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "6e9a99a3-62b1-4121-b7d7-148eaebbe9ba",
"name": "output",
"type": "string",
"value": "={{ $json.message }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "0535be0a-4aac-48a8-8909-41abca4944a0",
"name": "Simple Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
176,
48
],
"parameters": {
"sessionKey": "={{ $('LINE webhook').item.json.body.events[0].source.userId }}",
"sessionIdType": "customKey"
},
"typeVersion": 1.3
},
{
"id": "2851ca68-76c5-4b97-910d-2d4fa729754f",
"name": "Code",
"type": "n8n-nodes-base.code",
"position": [
352,
608
],
"parameters": {
"jsCode": "const inputData = $input.all();\nconst results = [];\n\nfor (let i = 0; i < inputData.length; i++) {\n try {\n\n let aiResponse = inputData[i].json.output || inputData[i].json.text || inputData[i].json.content || '';\n \n if (!aiResponse) {\n console.log(`\u30a2\u30a4\u30c6\u30e0 ${i}: \u7a7a\u306e\u5fdc\u7b54\u3092\u30b9\u30ad\u30c3\u30d7`);\n continue;\n }\n \n // \u6587\u5b57\u5217\u306e\u524d\u5f8c\u306e\u7a7a\u767d\u3092\u524a\u9664\n aiResponse = aiResponse.trim();\n \n // JSON\u306e\u524d\u5f8c\u306b\u3042\u308b\u4e0d\u8981\u306a\u30c6\u30ad\u30b9\u30c8\u3092\u524a\u9664\n // ```json \u3084 ``` \u3067\u56f2\u307e\u308c\u3066\u3044\u308b\u5834\u5408\u306e\u51e6\u7406\n if (aiResponse.includes('```json')) {\n const jsonStart = aiResponse.indexOf('```json') + 7;\n const jsonEnd = aiResponse.indexOf('```', jsonStart);\n if (jsonEnd !== -1) {\n aiResponse = aiResponse.substring(jsonStart, jsonEnd).trim();\n }\n } else if (aiResponse.includes('```')) {\n const jsonStart = aiResponse.indexOf('```') + 3;\n const jsonEnd = aiResponse.indexOf('```', jsonStart);\n if (jsonEnd !== -1) {\n aiResponse = aiResponse.substring(jsonStart, jsonEnd).trim();\n }\n }\n \n // JSON\u306e\u958b\u59cb\u3068\u7d42\u4e86\u3092\u898b\u3064\u3051\u308b\n const jsonStartIndex = aiResponse.indexOf('[');\n const jsonEndIndex = aiResponse.lastIndexOf(']');\n \n if (jsonStartIndex !== -1 && jsonEndIndex !== -1 && jsonEndIndex > jsonStartIndex) {\n const jsonString = aiResponse.substring(jsonStartIndex, jsonEndIndex + 1);\n \n try {\n // JSON\u3092\u30d1\u30fc\u30b9\n const parsedData = JSON.parse(jsonString);\n \n // \u914d\u5217\u3067\u306a\u3044\u5834\u5408\u306f\u914d\u5217\u306b\u5909\u63db\n const dataArray = Array.isArray(parsedData) ? parsedData : [parsedData];\n \n // \u5404\u30a2\u30a4\u30c6\u30e0\u3092\u51e6\u7406\n dataArray.forEach((item, index) => {\n // \u5fc5\u8981\u306a\u30d5\u30a3\u30fc\u30eb\u30c9\u304c\u5b58\u5728\u3059\u308b\u3053\u3068\u3092\u78ba\u8a8d\n if (item && typeof item === 'object') {\n const processedItem = {\n dishName: item.dishName || item.dish_name || item.name || 'Unknown',\n calories: item.calories || item.calorie || item.cal || 0,\n // \u5143\u306e\u30c7\u30fc\u30bf\u3082\u4fdd\u6301\uff08\u30c7\u30d0\u30c3\u30b0\u7528\uff09\n originalIndex: i,\n itemIndex: index,\n timestamp: new Date().toISOString()\n };\n \n // \u30ab\u30ed\u30ea\u30fc\u304c\u6570\u5024\u3067\u306a\u3044\u5834\u5408\u306f\u6570\u5024\u306b\u5909\u63db\u3092\u8a66\u884c\n if (typeof processedItem.calories === 'string') {\n const numericCalories = parseInt(processedItem.calories.replace(/[^\\d]/g, ''));\n processedItem.calories = isNaN(numericCalories) ? 0 : numericCalories;\n }\n \n results.push({ json: processedItem });\n }\n });\n \n } catch (parseError) {\n console.log(`\u30a2\u30a4\u30c6\u30e0 ${i}: JSON\u89e3\u6790\u30a8\u30e9\u30fc - ${parseError.message}`);\n console.log(`\u554f\u984c\u306e\u3042\u308bJSON\u6587\u5b57\u5217: ${jsonString}`);\n \n // JSON\u304c\u7121\u52b9\u306a\u5834\u5408\u3067\u3082\u3001\u624b\u52d5\u3067\u30c7\u30fc\u30bf\u3092\u62bd\u51fa\u3092\u8a66\u884c\n const manualExtract = extractDataManually(aiResponse);\n if (manualExtract) {\n results.push({ json: manualExtract });\n }\n }\n } else {\n console.log(`\u30a2\u30a4\u30c6\u30e0 ${i}: \u6709\u52b9\u306aJSON\u69cb\u9020\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093`);\n \n // \u624b\u52d5\u3067\u30c7\u30fc\u30bf\u3092\u62bd\u51fa\u3092\u8a66\u884c\n const manualExtract = extractDataManually(aiResponse);\n if (manualExtract) {\n results.push({ json: manualExtract });\n }\n }\n \n } catch (error) {\n console.log(`\u30a2\u30a4\u30c6\u30e0 ${i}: \u51e6\u7406\u30a8\u30e9\u30fc - ${error.message}`);\n \n // \u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u305f\u5834\u5408\u3067\u3082\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3067\u51e6\u7406\u3092\u7d99\u7d9a\n results.push({\n json: {\n dishName: 'Error',\n calories: 0,\n error: error.message,\n originalIndex: i,\n timestamp: new Date().toISOString()\n }\n });\n }\n}\n\n// \u624b\u52d5\u3067\u30c7\u30fc\u30bf\u3092\u62bd\u51fa\u3059\u308b\u95a2\u6570\nfunction extractDataManually(text) {\n try {\n const dishMatch = text.match(/\"dishName\"\\s*:\\s*\"([^\"]+)\"/i) || \n text.match(/\"dish_name\"\\s*:\\s*\"([^\"]+)\"/i) ||\n text.match(/\"name\"\\s*:\\s*\"([^\"]+)\"/i);\n \n const caloriesMatch = text.match(/\"calories\"\\s*:\\s*\"?(\\d+)\"?/i) ||\n text.match(/\"calorie\"\\s*:\\s*\"?(\\d+)\"?/i) ||\n text.match(/\"cal\"\\s*:\\s*\"?(\\d+)\"?/i);\n \n if (dishMatch || caloriesMatch) {\n return {\n dishName: dishMatch ? dishMatch[1] : 'Unknown',\n calories: caloriesMatch ? parseInt(caloriesMatch[1]) : 0,\n extractedManually: true,\n timestamp: new Date().toISOString()\n };\n }\n } catch (error) {\n console.log('\u624b\u52d5\u62bd\u51fa\u30a8\u30e9\u30fc:', error.message);\n }\n return null;\n}\n\n// \u7d50\u679c\u304c\u7a7a\u306e\u5834\u5408\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u51e6\u7406\nif (results.length === 0) {\n console.log('\u51e6\u7406\u53ef\u80fd\u306a\u30c7\u30fc\u30bf\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f');\n results.push({\n json: {\n dishName: 'No Data',\n calories: 0,\n error: 'No valid data found',\n timestamp: new Date().toISOString()\n }\n });\n}\n\nconsole.log(`\u51e6\u7406\u5b8c\u4e86: ${results.length} \u4ef6\u306e\u30a2\u30a4\u30c6\u30e0\u3092\u5909\u63db\u3057\u307e\u3057\u305f`);\nreturn results;"
},
"typeVersion": 2
},
{
"id": "bff30c01-da43-463d-940e-b396d8237c61",
"name": "Append row in sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
608,
608
],
"parameters": {
"columns": {
"value": {
"cal": "={{ $json.calories }}",
"date": "={{ $now.format('yyyy-MM-dd') }}",
"menu": "={{ $json.dishName }}"
},
"schema": [
{
"id": "date",
"type": "string",
"display": true,
"required": false,
"displayName": "date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "menu",
"type": "string",
"display": true,
"required": false,
"displayName": "menu",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "cal",
"type": "string",
"display": true,
"required": false,
"displayName": "cal",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1178520345,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1upzLlUvmXYgALLtUR6WEHUtxh064mDNrBVM-b3Of_vg/edit#gid=1178520345",
"cachedResultName": "11_test"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1upzLlUvmXYgALLtUR6WEHUtxh064mDNrBVM-b3Of_vg",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1upzLlUvmXYgALLtUR6WEHUtxh064mDNrBVM-b3Of_vg/edit?usp=drivesdk",
"cachedResultName": "kote2 n8n sheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.6
},
{
"id": "1bf7ef0f-1e8d-4e61-b3bd-aaaefcf15366",
"name": "Code1",
"type": "n8n-nodes-base.code",
"position": [
352,
272
],
"parameters": {
"jsCode": "// n8n Code node - AI\u30a8\u30fc\u30b8\u30a7\u30f3\u30c8\u304b\u3089\u306eJSON\u6587\u5b57\u5217\u3092LINE\u30e1\u30c3\u30bb\u30fc\u30b8\u306b\u5909\u63db\nconst inputData = $input.all();\nconst results = [];\n\nfor (let i = 0; i < inputData.length; i++) {\n try {\n // AI\u30a8\u30fc\u30b8\u30a7\u30f3\u30c8\u304b\u3089\u306e\u51fa\u529b\u3092\u53d6\u5f97\n let aiResponse = inputData[i].json.output || inputData[i].json.text || inputData[i].json.content || '';\n \n // \u7a7a\u306e\u5834\u5408\u306f\u30b9\u30ad\u30c3\u30d7\n if (!aiResponse) {\n console.log(`\u30a2\u30a4\u30c6\u30e0 ${i}: \u7a7a\u306e\u5fdc\u7b54\u3092\u30b9\u30ad\u30c3\u30d7`);\n continue;\n }\n \n // \u6587\u5b57\u5217\u306e\u524d\u5f8c\u306e\u7a7a\u767d\u3092\u524a\u9664\n aiResponse = aiResponse.trim();\n \n // JSON\u30c7\u30fc\u30bf\u3092\u62bd\u51fa\u3059\u308b\n const menuItems = extractMenuData(aiResponse);\n \n if (menuItems && menuItems.length > 0) {\n // LINE\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u751f\u6210\n const lineMessage = generateLineMessage(menuItems);\n \n results.push({\n json: {\n message: lineMessage,\n menuCount: menuItems.length,\n totalCalories: menuItems.reduce((sum, item) => sum + item.calories, 0),\n timestamp: new Date().toISOString(),\n originalIndex: i\n }\n });\n \n console.log(`\u30a2\u30a4\u30c6\u30e0 ${i}: ${menuItems.length}\u500b\u306e\u30e1\u30cb\u30e5\u30fc\u3092\u51e6\u7406\u3057\u307e\u3057\u305f`);\n } else {\n // \u98df\u54c1\u304c\u691c\u51fa\u3055\u308c\u306a\u304b\u3063\u305f\u5834\u5408\n results.push({\n json: {\n message: \"\u753b\u50cf\u304b\u3089\u98df\u54c1\u3092\u691c\u51fa\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\",\n menuCount: 0,\n totalCalories: 0,\n timestamp: new Date().toISOString(),\n originalIndex: i\n }\n });\n \n console.log(`\u30a2\u30a4\u30c6\u30e0 ${i}: \u98df\u54c1\u304c\u691c\u51fa\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f`);\n }\n \n } catch (error) {\n console.log(`\u30a2\u30a4\u30c6\u30e0 ${i}: \u51e6\u7406\u30a8\u30e9\u30fc - ${error.message}`);\n \n // \u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u305f\u5834\u5408\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\n results.push({\n json: {\n message: \"\u753b\u50cf\u306e\u89e3\u6790\u3067\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002\",\n menuCount: 0,\n totalCalories: 0,\n error: error.message,\n timestamp: new Date().toISOString(),\n originalIndex: i\n }\n });\n }\n}\n\n// JSON\u30c7\u30fc\u30bf\u3092\u62bd\u51fa\u3059\u308b\u95a2\u6570\nfunction extractMenuData(text) {\n const menuItems = [];\n \n try {\n // JSON\u306e\u524d\u5f8c\u306b\u3042\u308b\u4e0d\u8981\u306a\u30c6\u30ad\u30b9\u30c8\u3092\u524a\u9664\n let cleanText = text;\n \n // ```json \u3084 ``` \u3067\u56f2\u307e\u308c\u3066\u3044\u308b\u5834\u5408\u306e\u51e6\u7406\n if (cleanText.includes('```json')) {\n const jsonStart = cleanText.indexOf('```json') + 7;\n const jsonEnd = cleanText.indexOf('```', jsonStart);\n if (jsonEnd !== -1) {\n cleanText = cleanText.substring(jsonStart, jsonEnd).trim();\n }\n } else if (cleanText.includes('```')) {\n const jsonStart = cleanText.indexOf('```') + 3;\n const jsonEnd = cleanText.indexOf('```', jsonStart);\n if (jsonEnd !== -1) {\n cleanText = cleanText.substring(jsonStart, jsonEnd).trim();\n }\n }\n \n // JSON\u306e\u958b\u59cb\u3068\u7d42\u4e86\u3092\u898b\u3064\u3051\u308b\n const jsonStartIndex = cleanText.indexOf('[');\n const jsonEndIndex = cleanText.lastIndexOf(']');\n \n if (jsonStartIndex !== -1 && jsonEndIndex !== -1 && jsonEndIndex > jsonStartIndex) {\n const jsonString = cleanText.substring(jsonStartIndex, jsonEndIndex + 1);\n \n try {\n // JSON\u3092\u30d1\u30fc\u30b9\n const parsedData = JSON.parse(jsonString);\n \n // \u914d\u5217\u3067\u306a\u3044\u5834\u5408\u306f\u914d\u5217\u306b\u5909\u63db\n const dataArray = Array.isArray(parsedData) ? parsedData : [parsedData];\n \n // \u5404\u30a2\u30a4\u30c6\u30e0\u3092\u51e6\u7406\n dataArray.forEach((item) => {\n if (item && typeof item === 'object') {\n const dishName = item.dishName || item.dish_name || item.name || 'Unknown';\n let calories = item.calories || item.calorie || item.cal || 0;\n \n // \u30ab\u30ed\u30ea\u30fc\u304c\u6587\u5b57\u5217\u306e\u5834\u5408\u306f\u6570\u5024\u306b\u5909\u63db\n if (typeof calories === 'string') {\n const numericCalories = parseInt(calories.replace(/[^\\d]/g, ''));\n calories = isNaN(numericCalories) ? 0 : numericCalories;\n }\n \n // \u6709\u52b9\u306a\u30c7\u30fc\u30bf\u306e\u307f\u8ffd\u52a0\n if (dishName !== 'Unknown' && calories > 0) {\n menuItems.push({\n dishName: dishName,\n calories: calories\n });\n }\n }\n });\n \n } catch (parseError) {\n console.log('JSON\u89e3\u6790\u30a8\u30e9\u30fc:', parseError.message);\n // \u624b\u52d5\u3067\u30c7\u30fc\u30bf\u3092\u62bd\u51fa\u3092\u8a66\u884c\n const manualExtract = extractDataManually(text);\n if (manualExtract) {\n menuItems.push(manualExtract);\n }\n }\n } else {\n // \u624b\u52d5\u3067\u30c7\u30fc\u30bf\u3092\u62bd\u51fa\u3092\u8a66\u884c\n const manualExtract = extractDataManually(text);\n if (manualExtract) {\n menuItems.push(manualExtract);\n }\n }\n \n } catch (error) {\n console.log('\u30c7\u30fc\u30bf\u62bd\u51fa\u30a8\u30e9\u30fc:', error.message);\n }\n \n return menuItems;\n}\n\n// \u624b\u52d5\u3067\u30c7\u30fc\u30bf\u3092\u62bd\u51fa\u3059\u308b\u95a2\u6570\nfunction extractDataManually(text) {\n try {\n const dishMatch = text.match(/\"dishName\"\\s*:\\s*\"([^\"]+)\"/i) || \n text.match(/\"dish_name\"\\s*:\\s*\"([^\"]+)\"/i) ||\n text.match(/\"name\"\\s*:\\s*\"([^\"]+)\"/i);\n \n const caloriesMatch = text.match(/\"calories\"\\s*:\\s*\"?(\\d+)\"?/i) ||\n text.match(/\"calorie\"\\s*:\\s*\"?(\\d+)\"?/i) ||\n text.match(/\"cal\"\\s*:\\s*\"?(\\d+)\"?/i);\n \n if (dishMatch && caloriesMatch) {\n const calories = parseInt(caloriesMatch[1]);\n if (!isNaN(calories) && calories > 0) {\n return {\n dishName: dishMatch[1],\n calories: calories\n };\n }\n }\n } catch (error) {\n console.log('\u624b\u52d5\u62bd\u51fa\u30a8\u30e9\u30fc:', error.message);\n }\n return null;\n}\n\n// LINE\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u751f\u6210\u3059\u308b\u95a2\u6570\nfunction generateLineMessage(menuItems) {\n if (!menuItems || menuItems.length === 0) {\n return \"\u753b\u50cf\u304b\u3089\u98df\u54c1\u3092\u691c\u51fa\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\";\n }\n \n let message = \"\u30ab\u30ed\u30ea\u30fc\u8a08\u7b97\uff1a\\n\";\n \n // \u5404\u30e1\u30cb\u30e5\u30fc\u30a2\u30a4\u30c6\u30e0\u3092\u8ffd\u52a0\n menuItems.forEach((item) => {\n message += `\u30fb${item.dishName}(${item.calories}kcal)\\n`;\n });\n \n message += \"\u30b7\u30fc\u30c8\u306b\u8a18\u5165\u3057\u307e\u3057\u305f\";\n \n return message;\n}\n\n// \u7d50\u679c\u304c\u7a7a\u306e\u5834\u5408\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u51e6\u7406\nif (results.length === 0) {\n console.log('\u51e6\u7406\u53ef\u80fd\u306a\u30c7\u30fc\u30bf\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f');\n results.push({\n json: {\n message: \"\u753b\u50cf\u306e\u51e6\u7406\u3067\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002\",\n menuCount: 0,\n totalCalories: 0,\n timestamp: new Date().toISOString()\n }\n });\n}\n\nconsole.log(`LINE \u30e1\u30c3\u30bb\u30fc\u30b8\u751f\u6210\u5b8c\u4e86: ${results.length} \u4ef6\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u751f\u6210\u3057\u307e\u3057\u305f`);\nreturn results;"
},
"typeVersion": 2
},
{
"id": "56bfb625-6b6b-480d-a81d-55ad8a629c57",
"name": "LINE webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-1040,
144
],
"parameters": {
"path": "0980b700-284e-4e52-a33e-71e087eea37f",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "9e59d336-a880-42aa-b69c-66b445208d1c",
"name": "user verification",
"type": "n8n-nodes-base.if",
"position": [
-800,
144
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "28c9bc55-ef1b-4fee-8c22-a48b88a82c34",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.events[0].source.userId }}",
"rightValue": "{your id}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "11ddcfb0-a2e8-4002-ba64-bff77a4bf066",
"name": "images download",
"type": "n8n-nodes-base.httpRequest",
"position": [
-368,
272
],
"parameters": {
"url": "=https://api-data.line.me/v2/bot/message/{{ $json.body.events[0].message.id }}/content",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer {channel access toaken}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "b40c936c-5273-493a-822f-36b44de906b3",
"name": "only message",
"type": "n8n-nodes-base.set",
"position": [
-128,
-160
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "8504cdbe-9904-4e83-aee9-362f02a25014",
"name": "text",
"type": "string",
"value": "={{ $json.body.events[0].message.text }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "599b110f-7771-4497-a41f-b231c3ebc3b8",
"name": "send LINE",
"type": "n8n-nodes-base.httpRequest",
"position": [
1184,
128
],
"parameters": {
"url": "https://api.line.me/v2/bot/message/reply",
"method": "POST",
"options": {},
"jsonBody": "={\n \"replyToken\": \"{{ $('LINE webhook').item.json.body.events[0].replyToken }}\",\n \"messages\": [\n {\n \"type\": \"text\",\n \"text\":{{ JSON.stringify($json.output) }}\n }\n ]\n} ",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "978b63a0-fab0-445f-8f1b-0ab3b92f22b1",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1504,
-64
],
"parameters": {
"width": 368,
"height": 560,
"content": "## LINE Food Image Calorie Logger to Google Sheets\n\nThis workflow allows a LINE user to send either text or an image of food to a connected LINE bot.\n\n- **Text message** \u2192 The AI agent responds directly via LINE. \n- **Image message** \u2192 The workflow downloads it from LINE\u2019s API, analyzes it using OpenAI\u2019s Vision model, estimates calories **only if the image contains food**, and formats the result into JSON. \n- Detected dishes and calories are appended to a Google Sheet, and a confirmation message is sent back to the user via LINE.\n\n### Key Features\n- Integrates **LINE Messaging API** webhook with n8n\n- Uses **OpenAI Vision** to detect food and estimate calories\n- Automatically logs results into **Google Sheets**\n- Sends **real-time feedback** to the LINE user\n\n### How to Use\n1. Set up a LINE Messaging API channel and get your **channel access token**.\n2. Add your **OpenAI API credentials** in n8n.\n3. Replace placeholders for `{channel access token}`, `{your id}`, and **Google Sheet IDs** with your own.\n4. Activate the workflow and send a food image or text message to your LINE bot.\n"
},
"typeVersion": 1
},
{
"id": "78855553-f504-4ee0-913e-1b1c933f0cae",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
240,
192
],
"parameters": {
"color": 4,
"width": 704,
"height": 272,
"content": "## message to LINE"
},
"typeVersion": 1
},
{
"id": "e2180426-a161-4483-a630-63eb54cf0d29",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
240,
512
],
"parameters": {
"color": 3,
"width": 704,
"height": 272,
"content": "## write to Google Sheets"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "a7eb22fe-627c-4d1a-aaee-a05a9fb4eb81",
"connections": {
"Code": {
"main": [
[
{
"node": "Append row in sheet",
"type": "main",
"index": 0
}
]
]
},
"Code1": {
"main": [
[
{
"node": "Edit Fields1",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "only message",
"type": "main",
"index": 0
}
],
[
{
"node": "images download",
"type": "main",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[
{
"node": "send LINE",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields1": {
"main": [
[
{
"node": "send LINE",
"type": "main",
"index": 0
}
]
]
},
"LINE webhook": {
"main": [
[
{
"node": "user verification",
"type": "main",
"index": 0
}
]
]
},
"only message": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Analyze image": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
},
{
"node": "Code1",
"type": "main",
"index": 0
}
]
]
},
"Simple Memory": {
"ai_memory": [
[
{
"node": "AI Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"images download": {
"main": [
[
{
"node": "Analyze image",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"user verification": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2ApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow allows a LINE user to send either text or an image of food to a connected LINE bot.
Source: https://n8n.io/workflows/7228/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Universal Expense tracker. Uses telegram, httpRequest, openAi, googleSheets. Webhook trigger; 33 nodes.
This n8n workflow orchestrates a powerful suite of AI Agents and automations to manage and optimize various aspects of an e-commerce operation, particularly for platforms like Shopify. It leverages La
My workflow 15. Uses httpRequest, memoryBufferWindow, agent, lmChatOpenAi. Webhook trigger; 74 nodes.
leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.
This automation is designed to help you generate AI-powered music tracks, cover art, and fully rendered music videos — all triggered from a simple Telegram chat and managed via Google Sheets.