Skip to content

Instantly share code, notes, and snippets.

@mateusrovedaa
Last active November 10, 2025 17:01
Show Gist options
  • Select an option

  • Save mateusrovedaa/9a81c2ea328011684568aae89771c5d5 to your computer and use it in GitHub Desktop.

Select an option

Save mateusrovedaa/9a81c2ea328011684568aae89771c5d5 to your computer and use it in GitHub Desktop.
bot-telegram-n8n-finances
{
"name": "Bot ROVEEb",
"nodes": [
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $json.message.text }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"id": "0f04348c-a5af-4874-a8cd-8da747c5271f"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "text"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "a122a7ee-7f24-4d3b-aaf5-267688ed7176",
"leftValue": "={{ $json.message.voice.file_id }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "voice"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "507ebe51-f956-4fdd-bd2b-6589a7e3c206",
"leftValue": "={{ $json.message.document.file_id }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "csv"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
-2320,
-672
],
"id": "571cb7f5-a835-4bb7-8ea9-61792de2f361",
"name": "Switch"
},
{
"parameters": {
"resource": "audio",
"operation": "transcribe",
"options": {
"language": "pt"
}
},
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 1.8,
"position": [
-1888,
-656
],
"id": "1dbe237c-c086-4c74-a292-1cab20d9e640",
"name": "OpenAI",
"retryOnFail": true,
"credentials": {
"openAiApi": {
"id": "ASEhNATa5Q5yMLGg",
"name": "OpenAi account"
}
}
},
{
"parameters": {
"modelId": {
"__rl": true,
"value": "gpt-5-nano",
"mode": "list",
"cachedResultName": "GPT-5-NANO"
},
"responses": {
"values": [
{
"content": "=Você é um extrator de transações financeiras em PT-BR no n8n. Sua tarefa é LER o texto bruto e DEVOLVER apenas JSON válido seguindo o esquema abaixo.\n\nEntrada (texto bruto):\n{{$json[\"message\"]}}\n\nSaída (obrigatória, SOMENTE JSON válido):\n{\"transactions\":[ ... ]} \nSe nada for encontrado: {\"transactions\":[]}\n\nPara cada item em \"transactions\" use exatamente estas chaves:\n- categoria: UMA dentre {Alimentação,Transporte,Moradia,Lazer,Saúde,Compras,Serviços,Educação,Dívidas/Empréstimos,Cartão de Crédito,Viagem,Outros}. Se for ENTRADA, use \"Receita\".\n- descricao: resumo curto, normalizado e legível (ex.: \"Almoço no X\", \"Gasolina no Y\"). NÃO escreva “compra de”, “recebido de”, “categoria…”.\n- valor: número BRL com ponto decimal (ex.: 50.00). Converta valores por extenso (ex.: \"cinquenta reais\" → 50.00).\n- data: AAAA-MM-DD. Inferir “hoje/ontem/dia da semana” considerando hoje = {{$now.setZone('America/Sao_Paulo').toFormat('yyyy-MM-dd HH:mm:ss')}}. Se ausente, usar a data de hoje.\n- tipo: \"entrada\" (receita/salário/recebido) ou \"saida\" (despesa/compra/custo).\n\nREGRAS DE NORMALIZAÇÃO (aplique antes de classificar):\n1) Remova caracteres de ruído em nomes/descrições: asterisco (*) , barras (/), sublinhado (_), hífen isolado (- quando usado como separador), pontos isolados (.) e múltiplos espaços → substitua por um único espaço.\n2) Aparar espaços no início/fim.\n3) \"Formato de frase\": \n - Coloque iniciais de palavras em Maiúsculas e demais minúsculas (Title Case), mantendo minúsculas para conectivos/curtas: {de, da, do, das, dos, e, em, no, na, nos, nas, com, para, por}.\n - Preserve siglas comuns (2–4 letras) se vierem todas em maiúsculas (ex.: \"USP\", \"DM”, \"IFD\" permanecem maiúsculas).\n4) Exemplos:\n - \"PET LOVE*CLUBE\" → \"Pet Love Clube\"\n - \"UBER * PENDING\" → \"Uber Pending\"\n - \"FARMACIA SAO JOAO\" → \"Farmacia São João\" (não invente acentos se não souber)\n\nREGRAS DE VALOR:\n- Aceite formatos: \"R$ 3.032,81\", \"R$ -50,00\", \"50,00\", \"-12,06\", ou por extenso (\"cinquenta reais\").\n- Converter para número com ponto decimal: 3032.81, -50.00, etc.\n- Se o valor for negativo, respeite o sinal no número final.\n- Se houver mais de um valor no texto, escolha o da transação em questão.\n\nREGRAS DE DATA:\n- Se houver \"hoje/ontem/anteontem\" ou \"segunda/terça/...\": infira a data relativa usando a referência de \"hoje\" acima (America/Sao_Paulo).\n- Se a data estiver ausente, use a data de hoje.\n- Se houver dia e mês sem ano, use o ano corrente.\n\nREGRAS DE TIPO:\n- \"entrada\" se o texto indicar crédito/recebimento: {recebido, crédito, creditado, estorno, reembolso, salário, depósito, ajuste a crédito}.\n- Caso contrário, \"saida\".\n- Se o valor vier com sinal negativo mas o contexto indicar PAGAMENTO na fatura do cartão (ajuste a crédito por exemplo) ou indicar estorno/reembolso, classifique como \"entrada\" e lance o valor positivo.\n\nREGRAS DE CATEGORIA (heurísticas, escolha UMA):\n- Viagem: hospedagem/hotel/pousada/airbnb/booking, tarifas de viagem, passagens.\n- Alimentação: restaurante, café, iFood, padaria, mercado com itens de consumo imediato; \"almoço\", \"jantar\", \"lanche\".\n- Transporte: Uber/99, combustível (posto), pedágio, estacionamento.\n- Saúde: farmácia, clínica, exames, plano de saúde.\n- Compras: varejo geral (Amazon, Shopee, Magazine Luiza, etc.).\n- Serviços: assinaturas/serviços digitais (Spotify, Netflix, Wasabi, Contabo, AWS), manutenção/serviço recorrente.\n- Educação: escola, curso, mensalidade educacional.\n- Moradia: aluguel, condomínio, luz, água, internet residencial.\n- Lazer: cinema, eventos, entretenimento não alimentar.\n- Cartão de Crédito: tarifas e encargos explícitos do cartão (anuidade/IOF) quando não se encaixar melhor em outra.\n- Dívidas/Empréstimos: parcelas/financiamentos/empréstimos bancários.\n- Receita: para entradas.\n- Outros: caso não se enquadre nas anteriores.\n\nREGRAS DE FILTRO:\n- Ignore linhas que sejam totais, rodapés, cabeçalhos ou descrições genéricas sem transação (ex.: \"Pagamentos Validos Normais\", \"Total da Fatura\").\n\nFORMATO FINAL:\n- Extraia TODAS as transações que encontrar na entrada.\n- Não inclua comentários, textos extras ou quebras indevidas — SOMENTE o JSON válido pedido.\n- Quando houver hospedagem na descrição, categorize como \"Viagem\".\n\nExemplo de saída:\n{\"transactions\":[\n {\"categoria\":\"Compras\",\"descricao\":\"Pet Love Clube\",\"valor\":12.06,\"data\":\"2025-11-04\",\"tipo\":\"saida\"},\n {\"categoria\":\"Serviços\",\"descricao\":\"Spotify\",\"valor\":40.90,\"data\":\"2025-11-04\",\"tipo\":\"saida\"}\n]}\n"
}
]
},
"builtInTools": {},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 2,
"position": [
-1408,
-624
],
"id": "93d66d70-6ccd-4473-ac53-e3ab71275121",
"name": "Message a model",
"retryOnFail": true,
"credentials": {
"openAiApi": {
"id": "ASEhNATa5Q5yMLGg",
"name": "OpenAi account"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "ad1ddc47-fe87-4364-95ae-c9945a2dd4cf",
"name": "message",
"value": "={{ $json.text }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-1744,
-656
],
"id": "249efe3c-0b4d-4f59-9820-d1941062764b",
"name": "Set Message from Audio"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "6e1e69d7-6962-4646-a064-665ed8ea089e",
"name": "message",
"value": "={{ $json.message.text }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-1888,
-880
],
"id": "439990a1-fc59-4de4-a3a7-96dcf4e9e56b",
"name": "Set Message from Text"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "3038539e-653b-40da-920b-69ea72ec3dc0",
"name": "parsedJson",
"value": "={{ JSON.parse($json.output[0].content[0].text) }}",
"type": "object"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-928,
-624
],
"id": "571a82bb-62c7-4abc-96dc-a40f9515364f",
"name": "Parse AI JSON"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "fb611a02-1d8d-4726-b701-075738cbe52a",
"leftValue": "={{ $json.parsedJson.transactions.length }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-768,
-592
],
"id": "124574b2-c1c3-4590-a255-0d003bba7222",
"name": "Has transactions?"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "73c5a33d-8319-4b29-b377-cb0888c272ce",
"name": "message",
"value": "Não consegui encontrar nenhuma transação no seu áudio/texto. 🙁",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
48,
-480
],
"id": "168511b6-07b0-44e4-9adc-554874508983",
"name": "Fail message"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "73c5a33d-8319-4b29-b377-cb0888c272ce",
"name": "message",
"value": "=*{{ $json.user }}!* 👋\n\nSeu lançamento foi feito com sucesso:\n\n✅ *Tipo:* {{ $json.type }}\n🗓️ *Data:* {{ DateTime.fromISO($('Edit Fields').item.json.date).toFormat('dd/MM/yyyy') }}\n🧾 *Descrição:* {{ $json.description }}\n💰 *Valor:* R$ {{ $json.value.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}\n🏷️ *Categoria:* {{ $json.category }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
80,
-704
],
"id": "5fd4b4c1-1b8c-4dc0-b025-5f0627e15d04",
"name": "Sucess message"
},
{
"parameters": {
"dataTableId": {
"__rl": true,
"value": "rcz6CPiAZbvYcdSw",
"mode": "list",
"cachedResultName": "video",
"cachedResultUrl": "/projects/Fk5PIHrixMIVGbVy/datatables/rcz6CPiAZbvYcdSw"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"value": "={{ $json.value }}",
"date": "={{ $json.date }}",
"type": "={{ $json.type }}",
"category": "={{ $json.category }}",
"description": "={{ $json.description }}",
"user": "={{ $('Map Chat').item.json.user }}"
},
"matchingColumns": [],
"schema": [
{
"id": "value",
"displayName": "value",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"readOnly": false,
"removed": false
},
{
"id": "date",
"displayName": "date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "dateTime",
"readOnly": false,
"removed": false
},
{
"id": "type",
"displayName": "type",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "category",
"displayName": "category",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "description",
"displayName": "description",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "user",
"displayName": "user",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
-48,
-704
],
"id": "d119a50b-1760-4533-b506-974992f8b3ca",
"name": "Save"
},
{
"parameters": {
"content": "## Receive message",
"height": 272,
"width": 368,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-2816,
-768
],
"typeVersion": 1,
"id": "e6344816-956a-493f-a33d-3f2aa83c7321",
"name": "Sticky Note"
},
{
"parameters": {
"content": "## Extract informations",
"height": 1008,
"width": 1440,
"color": 2
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-2432,
-1056
],
"typeVersion": 1,
"id": "7cb26b1b-2330-48ef-a995-4b658f2da0f6",
"name": "Sticky Note1"
},
{
"parameters": {
"content": "## Store and response to telegram",
"height": 576,
"width": 1536,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-976,
-912
],
"typeVersion": 1,
"id": "1bae0ed3-36f1-43f9-85ac-befc9d150747",
"name": "Sticky Note2"
},
{
"parameters": {
"fieldToSplitOut": "parsedJson.transactions",
"options": {}
},
"type": "n8n-nodes-base.splitOut",
"typeVersion": 1,
"position": [
-624,
-752
],
"id": "a078b6db-bb12-4c13-a0c2-07d2d9f9ef2f",
"name": "Split Out"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "a3ba42dc-84ea-4833-acd8-e77f4f7d4ddf",
"name": "type",
"value": "={{ $json.tipo }}",
"type": "string"
},
{
"id": "fe50a7bf-d43c-43b0-bb2d-c0d9ac9cd909",
"name": "date",
"value": "={{ $json.data }}",
"type": "string"
},
{
"id": "f9afcd79-07ca-47fb-88f9-11b358e58417",
"name": "description",
"value": "={{ $json.descricao }}",
"type": "string"
},
{
"id": "2b5aa2b9-7349-4f6d-91e0-ec34e2416be7",
"name": "value",
"value": "={{ $json.valor }}",
"type": "string"
},
{
"id": "3c5215ad-b557-4772-a5b2-cc1997ae142b",
"name": "category",
"value": "={{ $json.categoria }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-192,
-704
],
"id": "c31405d8-08a3-4812-a1ad-63448098577e",
"name": "Edit Fields"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
-384,
-768
],
"id": "f6f84425-e963-4496-97eb-b7d6ab5ef12e",
"name": "Loop Over Items"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "42bd45d0-768b-4c47-bb1b-fdc9f5480025",
"leftValue": "={{ $json.message.document.mime_type }}",
"rightValue": "csv",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "12f09a33-f1b2-435b-88bf-0f593d50159f",
"leftValue": "={{ $json.message.document.file_name }}",
"rightValue": "csv",
"operator": {
"type": "string",
"operation": "endsWith"
}
}
],
"combinator": "or"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-2160,
-432
],
"id": "da8111a7-6770-406b-9405-4c5f7c10b6d3",
"name": "If"
},
{
"parameters": {
"resource": "file",
"fileId": "={{ $('Telegram Trigger').item.json.message.document.file_id }}",
"additionalFields": {}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-1952,
-448
],
"id": "dc9b8143-abf7-44ae-8b15-00c436900781",
"name": "Get CSV",
"webhookId": "bf319801-62af-4941-bd7b-c4487574477e",
"credentials": {
"telegramApi": {
"id": "sTNb3pmq5jE43YMZ",
"name": "Telegram account"
}
}
},
{
"parameters": {
"resource": "file",
"fileId": "={{ $('Telegram Trigger').item.json.message.voice.file_id }}",
"additionalFields": {}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-2032,
-656
],
"id": "76aa214a-c682-4a73-9a80-f7cfef756c63",
"name": "Get audio",
"webhookId": "bf319801-62af-4941-bd7b-c4487574477e",
"credentials": {
"telegramApi": {
"id": "sTNb3pmq5jE43YMZ",
"name": "Telegram account"
}
}
},
{
"parameters": {
"options": {
"rawData": true,
"readAsString": true
}
},
"id": "1860907f-6abe-4fee-8935-8afd0a0ecc04",
"name": "Convert To Spreadsheet",
"type": "n8n-nodes-base.spreadsheetFile",
"position": [
-1792,
-448
],
"typeVersion": 1
},
{
"parameters": {
"jsCode": "// ======= Configuração =======\nconst FIELD_CANDIDATES = {\n desc: ['Estabelecimento','estabelecimento','descricao','Descrição','title','Title','merchant','nome','estab'],\n amount: ['Valor','valor','valor_total','amount','Amount','price'],\n date: ['Data','data','date','Date'] // não usado na frase (usamos \"hoje\"), mas deixado para futura expansão\n};\n\nconst IGNORE_DESCRIPTIONS = [\n 'pagamentos validos normais',\n 'pagamento válido normal',\n 'pagamento recebido'\n];\n\n// ======= Helpers =======\nconst items = await $input.all();\n\nconst fmtHoje = new Intl.DateTimeFormat('pt-BR', { timeZone: 'America/Sao_Paulo' });\nconst hojeBR = fmtHoje.format(new Date());\n\n// remove BOM/acentos/caixa e normaliza espaços\nfunction normalizeKey(k){\n return String(k||'')\n .replace(/\\uFEFF/g,'')\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g,'')\n .toLowerCase().trim();\n}\nfunction normalizeText(s){\n return String(s||'')\n .replace(/\\uFEFF/g,'')\n .replace(/\\*/g,' ') // remove asteriscos\n .replace(/\\s+/g,' ') // compacta espaços\n .trim();\n}\nfunction normForCompare(s){\n return normalizeText(s)\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g,'')\n .toLowerCase();\n}\nfunction toTitleCase(s){\n return normalizeText(s).toLowerCase().replace(/\\b([a-zà-ú])([a-zà-ú]*)/gi, (_,a,b)=> a.toUpperCase()+b);\n}\n\nfunction pick(obj, candidates){\n // tenta direto\n for (const c of candidates) if (obj[c] !== undefined) return obj[c];\n // tenta por chaves normalizadas\n const map = new Map(Object.keys(obj).map(k => [normalizeKey(k), k]));\n for (const c of candidates){\n const nk = normalizeKey(c);\n if (map.has(nk)) return obj[map.get(nk)];\n }\n return undefined;\n}\n\n// \"R$ 3.032,81\" -> 3032.81 ; \"3032.81\" -> 3032.81 ; \"3.032,81\" -> 3032.81\nfunction parseAmountAny(s){\n if (s == null) return 0;\n const str = String(s).trim();\n // se tem vírgula como decimal (pt-BR)\n if (/,/.test(str) && !/^\\d+(\\.\\d+)?$/.test(str)){\n const cleaned = str.replace(/[R$\\s]/g,'').replace(/\\./g,'').replace(',', '.');\n const n = Number(cleaned);\n return Number.isFinite(n) ? n : 0;\n }\n // caso \"Nubank\" já venha número (ou string en-US)\n const n = Number(str.replace(/[R$\\s]/g,''));\n return Number.isFinite(n) ? n : 0;\n}\n\n// ======= Processamento =======\nconst lines = [];\n\nfor (const it of items){\n const row = it.json ?? it;\n\n const rawDesc = pick(row, FIELD_CANDIDATES.desc);\n const rawAmount = pick(row, FIELD_CANDIDATES.amount);\n\n const desc = toTitleCase(rawDesc || '');\n const descCmp = normForCompare(desc);\n\n // ignorar linhas específicas\n if (IGNORE_DESCRIPTIONS.includes(descCmp)) continue;\n\n const amount = parseAmountAny(rawAmount);\n if (!desc || !Number.isFinite(amount)) continue;\n\n const valorBR = Math.abs(amount).toLocaleString('pt-BR', { minimumFractionDigits:2, maximumFractionDigits:2 });\n lines.push(`Compra de ${valorBR} no ${desc} na data de ${hojeBR}`);\n}\n\n// ======= Saída preservando contexto =======\nconst baseBinary = items[0]?.binary ? { ...items[0].binary } : undefined;\n\nconst out = {\n json: { message: lines.join('\\n') },\n pairedItem: items.map((_, i) => ({ item: i }))\n};\nif (baseBinary) out.binary = baseBinary;\n\nreturn [out];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1616,
-448
],
"id": "a07ac806-c9f1-49ef-8785-7a51bd7cbff3",
"name": "Code in JavaScript"
},
{
"parameters": {
"jsCode": "const items = await $input.all();\n\nfunction pickName(from) {\n if (!from) return '';\n // tenta username; se não houver, usa \"first_name last_name\" (quando houver)\n const fname = from.first_name;\n return fname;\n}\n\nreturn items.map((it, idx) => {\n const j = it.json ?? it;\n\n const message = j.message ?? j; // aceita quando já está \"achatado\"\n const chat = message.chat ?? j.chat ?? {};\n const from = message.from ?? j.from ?? {};\n\n const chatId = chat.id ?? null;\n const chatType = chat.type ?? null;\n const chatTitle = chat.title ?? null;\n const fromId = from.id ?? null;\n const userLabel = pickName(from);\n\n return {\n json: {\n ...j,\n chat_id: chatId,\n chat_type: chatType,\n chat_title: chatTitle,\n from_id: fromId,\n user: userLabel\n },\n binary: it.binary,\n pairedItem: { item: idx }\n };\n});\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-2608,
-704
],
"id": "ec97b8f8-a3e6-49d5-b752-c3e699eb6eac",
"name": "Map Chat"
},
{
"parameters": {
"chatId": "={{ $('Map Chat').item.json.chat_id }}",
"text": "={{ $json.message }}",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
368,
-720
],
"id": "e1611863-a363-4687-9a74-5572bcdcddad",
"name": "Send message",
"webhookId": "d6d8c013-7f6f-4456-9c9e-894738f241c9",
"credentials": {
"telegramApi": {
"id": "sTNb3pmq5jE43YMZ",
"name": "Telegram account"
}
}
},
{
"parameters": {
"jsCode": "const items = await $input.all();\n\n// Coleta todas as mensagens\nconst msgs = items.map(i => i.json?.message).filter(Boolean);\n\n// Junta respeitando limite do Telegram\nconst MAX = 3800;\nconst out = [];\nlet bucket = \"\";\n\nfor (const m of msgs) {\n const piece = (bucket ? \"\\n\\n\" : \"\") + m;\n if ((bucket.length + piece.length) > MAX) {\n out.push({ json: { message: bucket } });\n bucket = m;\n } else {\n bucket += piece;\n }\n}\nif (bucket) out.push({ json: { message: bucket } });\n\nreturn out.map(o => ({\n ...o,\n pairedItem: [{ item: 0 }]\n}));\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-192,
-864
],
"id": "a8020ed1-f874-4d59-b864-8d5a31034456",
"name": "Aggregate"
},
{
"parameters": {
"content": "## Retrive data and generate HTML view",
"height": 416,
"width": 1376,
"color": 7
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-2832,
64
],
"typeVersion": 1,
"id": "34cb849c-8277-4799-9da0-4ddf0971a108",
"name": "Sticky Note3"
},
{
"parameters": {
"path": "finances-video",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
-2800,
224
],
"id": "ba31b11d-b969-4736-b21c-3b3004006057",
"name": "Webhook",
"webhookId": "e3067d29-7457-4335-ba08-57aebae41697"
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "rcz6CPiAZbvYcdSw",
"mode": "list",
"cachedResultName": "video",
"cachedResultUrl": "/projects/Fk5PIHrixMIVGbVy/datatables/rcz6CPiAZbvYcdSw"
},
"filters": {
"conditions": [
{
"keyName": "date",
"condition": "gte",
"keyValue": "={{ $json.start }}"
},
{
"keyName": "date",
"condition": "lte",
"keyValue": "={{ $json.end }}"
}
]
},
"returnAll": true
},
"name": "List Finance (Data Table)",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
-2288,
224
],
"id": "e5fd6190-dc90-47eb-a766-78e48c510dde",
"alwaysOutputData": true
},
{
"parameters": {
"jsCode": "// ================= helpers =================\nfunction isEntrada(t){ const v=(t||'').toLowerCase(); return ['entrada','receita','salario','salário','recebido','recebidos'].some(x=>v.includes(x)); }\nfunction isSaida(t){ const v=(t||'').toLowerCase(); return ['saida','saída','despesa','despesas','compra','custo'].some(x=>v.includes(x)); }\nfunction brl(x){ return (Number(x)||0).toLocaleString('pt-BR',{minimumFractionDigits:2, maximumFractionDigits:2}); }\nfunction normDate(s){ if(!s) return null; if(/^\\d{4}-\\d{2}$/.test(s)) return s + '-01'; return s; }\n\n// NÃO CONVERTE PARA Date: usa só a string YYYY-MM-DD\nfunction ymdToBR(ymd){\n const s = (ymd||'').slice(0,10);\n const [y,m,d] = s.split('-');\n return (y && m && d) ? `${d}/${m}/${y}` : '';\n}\n\nfunction ptMonthYear(ym){\n const [y, m] = (ym||'').split('-').map(Number);\n // Representação estática sem usar Date: mês por nome em pt-BR\n const meses = ['janeiro','fevereiro','março','abril','maio','junho','julho','agosto','setembro','outubro','novembro','dezembro'];\n if (!y || !m) return ym || '';\n return `${meses[m-1]} de ${y}`;\n}\n\n// ================ inputs =====================\nconst datesItem = $items('Set Infos', 0, 0)[0]?.json || {};\nconst fromStrIn = datesItem.start;\nconst toStrIn = datesItem.end;\nconst initialBalance = Number(datesItem.initialBalance ?? datesItem.ib ?? 0) || 0;\n\nconst from = normDate(fromStrIn);\nconst to = normDate(toStrIn) || new Date().toISOString().slice(0,10);\n\n// Limites em STRING (sem timezone)\nconst fromY = (from || '0000-00-00').slice(0,10);\nconst toY = (to || '9999-12-31').slice(0,10);\n\nif (!fromY || fromY === '0000-00-00') {\n return [{ json: { html_content: `<!doctype html><meta charset=\"utf-8\"><meta name=\"color-scheme\" content=\"light\"><body style=\"font:14px system-ui;background:#fff;color:#111\"><h3>Parâmetro ?start=AAAA-MM-DD (ou AAAA-MM) é obrigatório</h3></body>` } }];\n}\n\n// rows de entrada (Data Table -> Code)\nconst rows = (await $input.all()).map(i => i.json);\n\n// normaliza (sem parse de data)\nconst normalized = rows.map(r=>({\n date: r.date, // mantém original\n ymd: (r.date||'').slice(0,10), // YYYY-MM-DD (base para tudo)\n ym: (r.date||'').slice(0,7), // YYYY-MM\n value: Number(r.value)||0,\n type: r.type||'',\n description: r.description||'',\n category: r.category||'',\n user: r.user || ''\n}));\n\n// filtra por string (evita timezone)\nconst inRange = normalized.filter(r => r.ymd && r.ymd >= fromY && r.ymd <= toY);\n\n// meses presentes\nconst months = Array.from(new Set(inRange.map(x=>x.ym))).filter(Boolean).sort();\n\n// agregação mensal\nconst perMonth = months.map(m=>{\n const xs = inRange.filter(x=>x.ym===m);\n const recebidos = xs.filter(x=>isEntrada(x.type)).reduce((a,b)=>a+b.value,0);\n const despesas = xs.filter(x=>isSaida(x.type)).reduce((a,b)=>a+b.value,0);\n return { month:m, recebidos, despesas, saldo: recebidos - despesas };\n});\n\n// totais do período\nconst totalRecebidos = inRange.filter(x=>isEntrada(x.type)).reduce((a,b)=>a+b.value,0);\nconst totalDespesas = inRange.filter(x=>isSaida(x.type)).reduce((a,b)=>a+b.value,0);\nconst saldoPeriodo = totalRecebidos - totalDespesas;\n\n// —— saldo acumulado (sem converter datas) ——\nconst timeline = inRange\n .map(x => ({ ...x, delta: isEntrada(x.type) ? Number(x.value||0) : -Number(x.value||0) }))\n .sort((a,b) => a.ymd.localeCompare(b.ymd) || a.description.localeCompare(b.description));\n\nlet running = initialBalance;\nconst saldoAcumSeries = timeline.map(t => {\n running += t.delta;\n return { ymd: t.ymd, label: ymdToBR(t.ymd), balance: Number(running.toFixed(2)) };\n});\nconst saldoInicial = initialBalance;\nconst saldoFinal = Number((initialBalance + saldoPeriodo).toFixed(2));\n\n// ---- séries linha (recebidos×despesas) ----\nlet lineLabels = [];\nlet serieRecebidos = [];\nlet serieDespesas = [];\nconst isSingleMonth = months.length === 1;\n\nif (isSingleMonth) {\n const days = Array.from(new Set(inRange.map(x=>x.ymd))).sort(); // YYYY-MM-DD\n lineLabels = days.map(d => ymdToBR(d));\n const recMap = new Map(), desMap = new Map();\n for (const d of days) { recMap.set(d, 0); desMap.set(d, 0); }\n for (const it of inRange) {\n if (isEntrada(it.type)) recMap.set(it.ymd, (recMap.get(it.ymd) || 0) + it.value);\n else if (isSaida(it.type)) desMap.set(it.ymd, (desMap.get(it.ymd) || 0) + it.value);\n }\n serieRecebidos = days.map(d => Number((recMap.get(d)||0).toFixed(2)));\n serieDespesas = days.map(d => Number((desMap.get(d)||0).toFixed(2)));\n} else {\n lineLabels = perMonth.map(x=> ptMonthYear(x.month));\n serieRecebidos = perMonth.map(x=> Number(x.recebidos.toFixed(2)));\n serieDespesas = perMonth.map(x=> Number(x.despesas.toFixed(2)));\n}\n\n// ---- gráfico da direita (dinâmico) ----\nlet rightLabels, rightData, rightTitle;\nif (isSingleMonth) {\n rightLabels = ['Receitas', 'Despesas'];\n rightData = [Number(totalRecebidos.toFixed(2)), Number(totalDespesas.toFixed(2))];\n rightTitle = 'Receitas × Despesas';\n} else {\n rightLabels = perMonth.map(x => ptMonthYear(x.month));\n rightData = perMonth.map(x => Number(x.saldo.toFixed(2)));\n rightTitle = 'Saldo por mês';\n}\n\n// —— despesas por categoria (barras) ——\nconst catMap = new Map();\nfor (const r of inRange) {\n if (isSaida(r.type)) {\n const k = r.category || 'Sem categoria';\n catMap.set(k, (catMap.get(k) || 0) + (Number(r.value) || 0));\n }\n}\nconst catLabels = Array.from(catMap.keys());\nconst catValues = Array.from(catMap.values());\nconst catTitle = `Despesas por categoria ${isSingleMonth ? '(do mês)' : '(no período)'}`;\n\n// tabela saldos por mês\nconst saldoRows = perMonth.map(x =>\n `<tr>\n <td>${ptMonthYear(x.month)}</td>\n <td class=\"td-right\">R$ ${brl(x.recebidos)}</td>\n <td class=\"td-right\">R$ ${brl(x.despesas)}</td>\n <td class=\"td-right\" style=\"font-weight:600;color:${x.saldo>=0?'#1a7f37':'#c62828'}\">R$ ${brl(x.saldo)}</td>\n </tr>`\n).join('');\n\n// lançamentos (ordenados por data ASC)\nconst lancamentos = [...inRange].sort((a,b)=> a.ymd.localeCompare(b.ymd) || a.description.localeCompare(b.description));\nconst lancRows = lancamentos.map(x=>{\n const isIn = isEntrada(x.type);\n const color = isIn ? '#1a7f37' : '#c62828';\n const sign = isIn ? '+' : '-';\n return `<tr>\n <td>${ymdToBR(x.ymd)}</td>\n <td>${x.user || ''}</td>\n <td>${x.description || ''}</td>\n <td>${x.category || ''}</td>\n <td class=\"td-right\" style=\"color:${color};font-weight:600\">${sign} R$ ${brl(x.value)}</td>\n </tr>`;\n}).join('');\n\n// =================== HTML =====================\nconst html = `<!doctype html>\n<html lang=\"pt-BR\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<meta name=\"color-scheme\" content=\"light\">\n<title>Relatório Financeiro (${fromY} a ${toY})</title>\n<style>\n :root{\n --bg:#ffffff; --card:#ffffff; --text:#0b1220; --muted:#5f6b7a; --border:#e6eaf2;\n --pos:#1a7f37; --neg:#c62828; --shadow:0 8px 20px rgba(0,0,0,.06); --radius:14px; --wrap:1080px;\n }\n *{box-sizing:border-box}\n html,body{margin:0; background:#ffffff; color:var(--text); font:14px/1.45 ui-sans-serif,system-ui,Inter,Roboto,Arial;}\n .wrap{max-width:var(--wrap); margin:32px auto; padding:0 20px;}\n header{display:flex; align-items:center; justify-content:space-between; gap:16px; margin-bottom:18px;}\n h1{margin:0; font-weight:700; font-size:26px; letter-spacing:.2px;}\n .badge{display:inline-flex; gap:8px; align-items:center; padding:8px 12px; border:1px solid var(--border); border-radius:999px; background:#f3f4f6; color:#374151; font-weight:700;}\n .cards{display:grid;grid-template-columns:repeat(12,1fr); gap:16px; margin:18px 0;}\n .card{grid-column:span 12; background:var(--card); border:1px solid var(--border); border-radius:var(--radius); box-shadow:var(--shadow); padding:16px;}\n .kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:14px;}\n .kpi{background:var(--card); border:1px solid var(--border); border-radius:12px; padding:14px;}\n .kpi .label{color:var(--muted); font-size:12px;}\n .kpi .value{margin-top:6px; font-weight:800; font-size:22px;}\n .chart-2col{display:grid; grid-template-columns: 1fr 1fr; gap:16px;}\n .chart-box{border:1px solid var(--border); border-radius:12px; padding:12px; background:var(--card);}\n .chart-title{margin:0 0 10px 0; font-weight:700; font-size:16px;}\n @media (max-width: 900px){ .chart-2col{grid-template-columns:1fr} }\n\n .table-wrap{overflow:auto; border:1px solid var(--border); border-radius:12px; background:var(--card);}\n table{width:100%; border-collapse:separate; border-spacing:0; min-width:720px;}\n thead th{position:sticky; top:0; background:#ffffff; color:var(--text); text-align:left; z-index:1; border-bottom:1px solid var(--border);}\n th, td{padding:10px 12px;}\n tbody tr:nth-child(odd){background-color:#00000005;}\n tbody tr:hover{background:#00000008;}\n .td-right{text-align:right;}\n</style>\n</head>\n<body>\n <div class=\"wrap\">\n <header>\n <h1>Relatório Financeiro</h1>\n <span class=\"badge\">Período: ${fromY} → ${toY}</span>\n </header>\n\n <section class=\"cards\">\n <div class=\"card\">\n <div class=\"kpis\">\n <div class=\"kpi\"><div class=\"label\">Saldo Inicial</div><div class=\"value\">R$ ${brl(saldoInicial)}</div></div>\n <div class=\"kpi\"><div class=\"label\">Total Recebidos</div><div class=\"value\" style=\"color:var(--pos)\">R$ ${brl(totalRecebidos)}</div></div>\n <div class=\"kpi\"><div class=\"label\">Total Despesas</div><div class=\"value\" style=\"color:var(--neg)\">R$ ${brl(totalDespesas)}</div></div>\n <div class=\"kpi\"><div class=\"label\">Saldo no Período</div><div class=\"value\" style=\"color:${saldoPeriodo>=0?'var(--pos)':'var(--neg)'}\">R$ ${brl(saldoPeriodo)}</div></div>\n <div class=\"kpi\"><div class=\"label\">Saldo Final</div><div class=\"value\" style=\"color:${saldoFinal>=0?'var(--pos)':'var(--neg)'}\">R$ ${brl(saldoFinal)}</div></div>\n </div>\n </div>\n\n <div class=\"card\">\n <div class=\"chart-2col\">\n <div class=\"chart-box\">\n <h3 class=\"chart-title\">${isSingleMonth ? 'Recebidos × Despesas por dia' : 'Recebidos × Despesas por mês'}</h3>\n <div style=\"height:260px\"><canvas id=\"chartLine\"></canvas></div>\n </div>\n <div class=\"chart-box\">\n <h3 class=\"chart-title\">${isSingleMonth ? 'Receitas × Despesas' : 'Saldo por mês'}</h3>\n <div style=\"height:260px\"><canvas id=\"chartRight\"></canvas></div>\n </div>\n </div>\n </div>\n\n <div class=\"card\">\n <h3 class=\"chart-title\">${catTitle}</h3>\n ${\n catLabels.length\n ? `<div style=\"height:260px\"><canvas id=\"chartCat\"></canvas></div>`\n : `<div style=\"padding:8px;color:#5f6b7a\">Sem despesas no período.</div>`\n }\n </div>\n\n <div class=\"card\">\n <h3 class=\"chart-title\">Saldo por mês</h3>\n <div class=\"table-wrap\">\n <table>\n <thead><tr><th>Mês</th><th class=\"td-right\">Recebidos</th><th class=\"td-right\">Despesas</th><th class=\"td-right\">Saldo</th></tr></thead>\n <tbody>${perMonth.length ? saldoRows : '<tr><td colspan=\"4\" style=\"padding:16px;color:#5f6b7a\">Sem dados no período.</td></tr>'}</tbody>\n </table>\n </div>\n </div>\n\n <div class=\"card\">\n <h3 class=\"chart-title\">Lançamentos</h3>\n <div class=\"table-wrap\">\n <table>\n <thead><tr><th>Data</th><th>Quem</th><th>Descrição</th><th>Categoria</th><th class=\"td-right\">Valor</th></tr></thead>\n <tbody>${lancRows || '<tr><td colspan=\"5\" style=\"padding:16px;color:#5f6b7a\">Sem lançamentos.</td></tr>'}</tbody>\n </table>\n </div>\n </div>\n </section>\n </div>\n\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"><\\/script>\n<script>\n(() => {\n if (!window.Chart) return;\n\n Chart.defaults.color = '#0b1220';\n const grid = '#e6eaf2';\n const tick = '#5f6b7a';\n Chart.defaults.scales = Chart.defaults.scales || {};\n Chart.defaults.scales.x = { grid: { color: grid }, ticks: { color: tick } };\n Chart.defaults.scales.y = { grid: { color: grid }, ticks: { color: tick }, beginAtZero:true };\n\n // --- line (Recebidos × Despesas) ---\n const lineCtx = document.getElementById('chartLine').getContext('2d');\n new Chart(lineCtx, {\n type: 'line',\n data: {\n labels: ${JSON.stringify(lineLabels)},\n datasets: [\n { label: 'Recebidos', data: ${JSON.stringify(serieRecebidos)}, borderWidth: 2, tension: 0.25, pointRadius: 3 },\n { label: 'Despesas', data: ${JSON.stringify(serieDespesas)}, borderWidth: 2, tension: 0.25, pointRadius: 3 }\n ]\n },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n plugins: { legend: { position: 'bottom', labels: { color: '#0b1220' } } },\n scales: {\n x:{ grid:{ color: grid }, ticks:{ color: tick } },\n y:{ grid:{ color: grid }, ticks:{ color: tick }, beginAtZero:true }\n }\n }\n });\n\n // --- right (pizza 1 mês; barras empilhadas >1 mês) ---\n const rightCtx = document.getElementById('chartRight').getContext('2d');\n const isSingle = ${JSON.stringify(isSingleMonth)};\n const labelsRight = ${JSON.stringify(rightLabels)};\n const dataArrRight = ${JSON.stringify(rightData)};\n\n const baseOptions = {\n responsive: true,\n maintainAspectRatio: false,\n plugins: { legend: { position: 'bottom', labels: { color: '#0b1220' } } }\n };\n\n if (isSingle) {\n new Chart(rightCtx, {\n type: 'pie',\n data: { labels: labelsRight, datasets: [{ data: dataArrRight }] },\n options: baseOptions\n });\n } else {\n const positivos = dataArrRight.map(v => v > 0 ? v : 0);\n const negativos = dataArrRight.map(v => v < 0 ? v : 0);\n\n new Chart(rightCtx, {\n type: 'bar',\n data: {\n labels: labelsRight,\n datasets: [\n { label: 'Positivo', data: positivos, backgroundColor: 'rgba(26,127,55,0.25)', borderColor: '#1a7f37', borderWidth: 1, stack: 'saldo' },\n { label: 'Negativo', data: negativos, backgroundColor: 'rgba(198,40,40,0.20)', borderColor: '#c62828', borderWidth: 1, stack: 'saldo' }\n ]\n },\n options: {\n ...baseOptions,\n scales: {\n x: { stacked: true, grid: { color: grid }, ticks: { color: tick } },\n y: { stacked: true, grid: { color: grid }, ticks: { color: tick } }\n }\n }\n });\n }\n\n // --- despesas por categoria (barras) ---\n const catLabels = ${JSON.stringify(catLabels)};\n const catValues = ${JSON.stringify(catValues)};\n if (catLabels.length) {\n const catCtx = document.getElementById('chartCat').getContext('2d');\n new Chart(catCtx, {\n type: 'bar',\n data: {\n labels: catLabels,\n datasets: [\n { label: 'Despesas', data: catValues, borderWidth: 1 }\n ]\n },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n plugins: { legend: { position: 'bottom', labels: { color: '#0b1220' } } },\n scales: {\n x: { grid: { color: grid }, ticks: { color: tick } },\n y: { grid: { color: grid }, ticks: { color: tick }, beginAtZero: true }\n }\n }\n });\n }\n})();\n<\\/script>\n</body>\n</html>`;\n\nreturn [{ json: { html_content: html } }];\n"
},
"name": "Generate HTML",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-2032,
224
],
"id": "6434bb32-7f20-4e61-bc44-c6874c0219f5"
},
{
"parameters": {
"keepOnlySet": true,
"values": {
"string": [
{
"name": "start",
"value": "={{ $json.query.start ?? '2024-01-01' }}"
},
{
"name": "end",
"value": "={{ $json.query.end ?? (new Date().toISOString().slice(0,10)) }}"
}
],
"number": [
{
"name": "initialBalance",
"value": "={{ Number($json.query.ib) ?? 0 }}"
}
]
},
"options": {}
},
"name": "Set Infos",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
-2544,
224
],
"id": "0f5bcd37-894d-476a-a148-44f3d21ebc86"
},
{
"parameters": {
"respondWith": "text",
"responseBody": "={{ $json[\"html_content\"] }}",
"options": {}
},
"name": "Response with HTML",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
-1728,
224
],
"id": "4c7bbd6b-aeb2-44af-a910-e7018e822551"
},
{
"parameters": {
"updates": [
"message"
],
"additionalFields": {}
},
"type": "n8n-nodes-base.telegramTrigger",
"typeVersion": 1.2,
"position": [
-2768,
-704
],
"id": "2730c018-093d-4d18-8332-600dda1f0354",
"name": "Telegram Trigger",
"webhookId": "7a1949da-594b-4f86-825a-7ca277efad50",
"credentials": {
"telegramApi": {
"id": "sTNb3pmq5jE43YMZ",
"name": "Telegram account"
}
}
}
],
"pinData": {},
"connections": {
"Switch": {
"main": [
[
{
"node": "Set Message from Text",
"type": "main",
"index": 0
}
],
[
{
"node": "Get audio",
"type": "main",
"index": 0
}
],
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"OpenAI": {
"main": [
[
{
"node": "Set Message from Audio",
"type": "main",
"index": 0
}
]
]
},
"Message a model": {
"main": [
[
{
"node": "Parse AI JSON",
"type": "main",
"index": 0
}
]
]
},
"Set Message from Audio": {
"main": [
[
{
"node": "Message a model",
"type": "main",
"index": 0
}
]
]
},
"Set Message from Text": {
"main": [
[
{
"node": "Message a model",
"type": "main",
"index": 0
}
]
]
},
"Parse AI JSON": {
"main": [
[
{
"node": "Has transactions?",
"type": "main",
"index": 0
}
]
]
},
"Has transactions?": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
}
],
[
{
"node": "Fail message",
"type": "main",
"index": 0
}
]
]
},
"Fail message": {
"main": [
[
{
"node": "Send message",
"type": "main",
"index": 0
}
]
]
},
"Sucess message": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Save": {
"main": [
[
{
"node": "Sucess message",
"type": "main",
"index": 0
}
]
]
},
"Split Out": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Save",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
],
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"If": {
"main": [
[
{
"node": "Get CSV",
"type": "main",
"index": 0
}
]
]
},
"Get CSV": {
"main": [
[
{
"node": "Convert To Spreadsheet",
"type": "main",
"index": 0
}
]
]
},
"Get audio": {
"main": [
[
{
"node": "OpenAI",
"type": "main",
"index": 0
}
]
]
},
"Convert To Spreadsheet": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "Message a model",
"type": "main",
"index": 0
}
]
]
},
"Map Chat": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "Send message",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Set Infos",
"type": "main",
"index": 0
}
]
]
},
"List Finance (Data Table)": {
"main": [
[
{
"node": "Generate HTML",
"type": "main",
"index": 0
}
]
]
},
"Generate HTML": {
"main": [
[
{
"node": "Response with HTML",
"type": "main",
"index": 0
}
]
]
},
"Set Infos": {
"main": [
[
{
"node": "List Finance (Data Table)",
"type": "main",
"index": 0
}
]
]
},
"Telegram Trigger": {
"main": [
[
{
"node": "Map Chat",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "3f5ea76e-6c0d-4c4c-856b-b776c00eae32",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"id": "N1cHhvnFycILn5LP",
"tags": []
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment