8
n8n 中文网amn8n.com

使用 Telegram 和 Google Sheets 通过 OCR.space 从收据照片跟踪支出

中级

这是一个Invoice Processing, AI Chatbot领域的自动化工作流,包含 14 个节点。主要使用 If, Function, HttpRequest, TelegramBot, GoogleSheets 等节点。 使用 Telegram 和 Google Sheets 通过 OCR.space 从收据照片跟踪支出

前置要求
  • 可能需要目标 API 的认证凭证
  • Telegram Bot Token
  • Google Sheets API 凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移

无法加载工作流预览

导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "nodes": [
    {
      "name": "Telegram Bot (Webhook)",
      "type": "n8n-nodes-base.telegramBot",
      "position": [
        250,
        200
      ],
      "parameters": {
        "webhookUrl": "=https://api.telegram.org/bot{{$env.YOUR_TELEGRAM_BOT_TOKEN}}/getUpdates",
        "pollInterval": 5,
        "allowedUpdates": [
          "message"
        ],
        "onlyNewUpdates": true
      },
      "typeVersion": 1
    },
    {
      "name": "检查'收入'",
      "type": "n8n-nodes-base.if",
      "position": [
        450,
        100
      ],
      "parameters": {
        "conditions": [
          {
            "value1": "={{$json.message.text}}",
            "value2": "income",
            "operation": "contains"
          }
        ]
      },
      "typeVersion": 1
    },
    {
      "name": "检查'支出'(文本)",
      "type": "n8n-nodes-base.if",
      "position": [
        450,
        300
      ],
      "parameters": {
        "conditions": [
          {
            "value1": "={{$json.message.text}}",
            "value2": "expense",
            "operation": "contains"
          }
        ]
      },
      "typeVersion": 1
    },
    {
      "name": "添加收入到 Google Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        700,
        100
      ],
      "parameters": {
        "operation": "appendRow",
        "sheetName": "Income",
        "columnValues": {
          "mapping": {
            "Date": "={{$today}}",
            "Type": "Income",
            "Amount": "={{$json.message.text.split(' ')[2]}}",
            "Source": "Manual",
            "Description": "={{$json.message.text.split(' ')[1]}}"
          }
        },
        "spreadsheetId": "={{$env.YOUR_GOOGLE_SHEET_ID}}",
        "authentication": "oAuth2",
        "valueInputOption": "USER_ENTERED"
      },
      "typeVersion": 1
    },
    {
      "name": "添加支出到 Google Sheet (文本)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        700,
        300
      ],
      "parameters": {
        "operation": "appendRow",
        "sheetName": "Expenses",
        "columnValues": {
          "mapping": {
            "Date": "={{$today}}",
            "Type": "Expense",
            "Amount": "={{$json.message.text.split(' ')[2]}}",
            "Source": "Manual",
            "Category": "={{$json.message.text.split(' ')[3]}}",
            "Description": "={{$json.message.text.split(' ')[1]}}"
          }
        },
        "spreadsheetId": "={{$env.YOUR_GOOGLE_SHEET_ID}}",
        "authentication": "oAuth2",
        "valueInputOption": "USER_ENTERED"
      },
      "typeVersion": 1
    },
    {
      "name": "发送收入确认",
      "type": "n8n-nodes-base.telegramBot",
      "position": [
        950,
        100
      ],
      "parameters": {
        "text": "Income \"{{$json.message.text.split(' ')[1]}}\" of ${{parseFloat($json.message.text.split(' ')[2]).toLocaleString('en-US')}} successfully recorded!",
        "chatId": "={{$json.message.chat.id}}",
        "parseMode": "HTML"
      },
      "typeVersion": 1
    },
    {
      "name": "发送支出确认 (文本)",
      "type": "n8n-nodes-base.telegramBot",
      "position": [
        950,
        300
      ],
      "parameters": {
        "text": "Expense \"{{$json.message.text.split(' ')[1]}}\" of ${{parseFloat($json.message.text.split(' ')[2]).toLocaleString('en-US')}} for category \"{{$json.message.text.split(' ')[3]}}\" successfully recorded!",
        "chatId": "={{$json.message.chat.id}}",
        "parseMode": "HTML"
      },
      "typeVersion": 1
    },
    {
      "name": "发送错误消息 (文本)",
      "type": "n8n-nodes-base.telegramBot",
      "position": [
        700,
        700
      ],
      "parameters": {
        "text": "Sorry, message format not recognized. Use format:\n*income [description] [amount]*\nor\n*expense [description] [amount] [category]*\nAlternatively, send a photo of your receipt for automatic tracking!",
        "chatId": "={{$json.message.chat.id}}",
        "parseMode": "HTML"
      },
      "typeVersion": 1
    },
    {
      "name": "检查'照片'",
      "type": "n8n-nodes-base.if",
      "position": [
        450,
        500
      ],
      "parameters": {
        "conditions": [
          {
            "value1": "={{$json.message.photo}}",
            "value2": "true",
            "operation": "isNotEmpty"
          }
        ]
      },
      "typeVersion": 1
    },
    {
      "name": "获取 Telegram 照片",
      "type": "n8n-nodes-base.telegramBot",
      "position": [
        700,
        500
      ],
      "parameters": {
        "chatId": "={{$json.message.chat.id}}",
        "fileId": "={{$json.message.photo[{$json.message.photo.length - 1}].file_id}}",
        "operation": "getFile"
      },
      "typeVersion": 1
    },
    {
      "name": "OCR.space 请求",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        950,
        500
      ],
      "parameters": {
        "url": "https://api.ocr.space/parse/image",
        "method": "POST",
        "options": {
          "query": [
            {
              "name": "apikey",
              "value": "={{$env.YOUR_OCR_SPACE_API_KEY}}"
            },
            {
              "name": "language",
              "value": "eng"
            },
            {
              "name": "isOverlayRequired",
              "value": "true"
            }
          ],
          "bodyParameters": [
            {
              "name": "file",
              "value": "={{$binary.data}}"
            }
          ],
          "bodyContentType": "formData"
        }
      },
      "typeVersion": 1
    },
    {
      "name": "解析 OCR 数据",
      "type": "n8n-nodes-base.function",
      "position": [
        1200,
        500
      ],
      "parameters": {
        "function": "const ocrData = $json.ParsedResults[0].ParsedText;\n\n// Basic regex to find common patterns for amount ($) and dates (MM/DD/YYYY, YYYY-MM-DD, DD/MM/YYYY)\nconst amountRegex = /(?:[USD|SGD|MYR|\\$])?\\s*(\\d{1,3}(?:[.,]\\d{3})*(?:[.,]\\d{2})?)/i;\nconst dateRegex = /(\\d{1,2}[-/.]\\d{1,2}[-/.]\\d{2,4})/;\n\nlet description = 'OCR Expense';\nlet amount = 0;\nlet date = new Date().toISOString().slice(0, 10); // Default to today\nlet category = 'Uncategorized';\n\nconst amountMatch = ocrData.match(amountRegex);\nif (amountMatch && amountMatch[1]) {\n  amount = parseFloat(amountMatch[1].replace(/[,.]/g, m => m === ',' ? '.' : ',')).toFixed(2); // Handle both comma and dot decimals\n}\n\nconst dateMatch = ocrData.match(dateRegex);\nif (dateMatch && dateMatch[1]) {\n  // Attempt to parse date, try common formats\n  const dateStr = dateMatch[1];\n  let parsedDate = null;\n\n  // Try YYYY-MM-DD\n  if (dateStr.match(/^\\d{4}[-/.]\\d{1,2}[-/.]\\d{1,2}$/)) {\n    parsedDate = new Date(dateStr);\n  }\n  // Try MM/DD/YYYY or DD/MM/YYYY (be careful with ambiguity)\n  else if (dateStr.match(/^\\d{1,2}[-/.]\\d{1,2}[-/.]\\d{2,4}$/)) {\n    // Assuming MM/DD/YYYY for simplicity, adjust if DD/MM/YYYY is more common for you\n    const parts = dateStr.split(/[-/.]/);\n    if (parts.length === 3) {\n      parsedDate = new Date(`${parts[2]}-${parts[0]}-${parts[1]}`); // YYYY-MM-DD format for constructor\n    }\n  }\n\n  if (parsedDate && !isNaN(parsedDate.getTime())) {\n    date = parsedDate.toISOString().slice(0, 10);\n  }\n}\n\n// Simple keyword-based categorization (you'd make this more robust)\nif (ocrData.toLowerCase().includes('food') || ocrData.toLowerCase().includes('restaurant') || ocrData.toLowerCase().includes('cafe')) {\n  category = 'Food & Drinks';\n} else if (ocrData.toLowerCase().includes('transport') || ocrData.toLowerCase().includes('uber') || ocrData.toLowerCase().includes('taxi')) {\n  category = 'Transportation';\n} else if (ocrData.toLowerCase().includes('shopping') || ocrData.toLowerCase().includes('store')) {\n  category = 'Shopping';\n} else if (ocrData.toLowerCase().includes('utility') || ocrData.toLowerCase().includes('electric') || ocrData.toLowerCase().includes('water')) {\n  category = 'Utilities';\n}\n\n// Try to get a simple description from the text (e.g., first few words of the text)\nconst lines = ocrData.split('\\n').filter(line => line.trim() !== '');\nif (lines.length > 0) {\n  description = lines[0].substring(0, Math.min(lines[0].length, 50)); // Take first 50 chars of the first non-empty line\n}\n\nreturn [\n  {\n    json: {\n      description: description,\n      amount: amount,\n      date: date,\n      category: category\n    }\n  }\n];"
      },
      "typeVersion": 1
    },
    {
      "name": "添加支出到 Google Sheet (OCR)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1450,
        500
      ],
      "parameters": {
        "operation": "appendRow",
        "sheetName": "Expenses",
        "columnValues": {
          "mapping": {
            "Date": "={{$json.date}}",
            "Type": "Expense",
            "Amount": "={{$json.amount}}",
            "Source": "OCR",
            "Category": "={{$json.category}}",
            "Description": "={{$json.description}}"
          }
        },
        "spreadsheetId": "={{$env.YOUR_GOOGLE_SHEET_ID}}",
        "authentication": "oAuth2",
        "valueInputOption": "USER_ENTERED"
      },
      "typeVersion": 1
    },
    {
      "name": "发送支出确认 (OCR)",
      "type": "n8n-nodes-base.telegramBot",
      "position": [
        1700,
        500
      ],
      "parameters": {
        "text": "Expense from receipt successfully recorded:\nDescription: *{{$json.description}}*\nAmount: *${{parseFloat($json.amount).toLocaleString('en-US')}}*\nCategory: *{{$json.category}}*",
        "chatId": "={{$json.message.chat.id}}",
        "parseMode": "HTML"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Check 'Photo'": {
      "output": [
        {
          "node": "Get Telegram Photo",
          "type": "main",
          "index": 0
        },
        {
          "node": "Send Error Message (Text)",
          "type": "main",
          "index": 1
        }
      ]
    },
    "Check 'Income'": {
      "output": [
        {
          "node": "Add Income to Google Sheet",
          "type": "main",
          "index": 0
        },
        {
          "node": "Send Error Message (Text)",
          "type": "main",
          "index": 1
        }
      ]
    },
    "Parse OCR Data": {
      "output": [
        {
          "node": "Add Expense to Google Sheet (OCR)",
          "type": "main",
          "index": 0
        }
      ]
    },
    "OCR.space Request": {
      "output": [
        {
          "node": "Parse OCR Data",
          "type": "main",
          "index": 0
        }
      ]
    },
    "Get Telegram Photo": {
      "output": [
        {
          "node": "OCR.space Request",
          "type": "main",
          "index": 0
        }
      ]
    },
    "Check 'Expense' (Text)": {
      "output": [
        {
          "node": "Add Expense to Google Sheet (Text)",
          "type": "main",
          "index": 0
        },
        {
          "node": "Send Error Message (Text)",
          "type": "main",
          "index": 1
        }
      ]
    },
    "Telegram Bot (Webhook)": {
      "output": [
        {
          "node": "Check 'Income'",
          "type": "main",
          "index": 0
        },
        {
          "node": "Check 'Expense' (Text)",
          "type": "main",
          "index": 0
        },
        {
          "node": "Check 'Photo'",
          "type": "main",
          "index": 0
        }
      ]
    },
    "Add Income to Google Sheet": {
      "output": [
        {
          "node": "Send Income Confirmation",
          "type": "main",
          "index": 0
        }
      ]
    },
    "Add Expense to Google Sheet (OCR)": {
      "output": [
        {
          "node": "Send Expense Confirmation (OCR)",
          "type": "main",
          "index": 0
        }
      ]
    },
    "Add Expense to Google Sheet (Text)": {
      "output": [
        {
          "node": "Send Expense Confirmation (Text)",
          "type": "main",
          "index": 0
        }
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

中级 - 发票处理, AI 聊天机器人

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
中级
节点数量14
分类2
节点类型5
难度说明

适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

外部链接
在 n8n.io 查看

分享此工作流