This workflow corresponds to n8n.io template #15944 — we link there as the canonical source.
This workflow follows the Agent → Google Drive 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": "7DkUhZxnBGbzTgpK",
"name": "Unified_AstroBot_Engine_With_Superbase_And_Telegram_Bot",
"tags": [],
"nodes": [
{
"id": "168eae06-6d9f-4d76-a692-e3db09fff24f",
"name": "Telegram Trigger",
"type": "n8n-nodes-base.telegramTrigger",
"position": [
-1648,
176
],
"parameters": {
"updates": [
"message",
"callback_query"
],
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "8ecc23bc-1a4c-4050-83c6-cc1a6c9115ab",
"name": "Supabase: Get Session",
"type": "n8n-nodes-base.supabase",
"position": [
-1424,
176
],
"parameters": {
"filters": {
"conditions": [
{
"keyName": "chat_id",
"keyValue": "={{ $json.message?.chat?.id?.toString() || $json.callback_query?.message?.chat?.id?.toString() }}"
}
]
},
"tableId": "sessions",
"operation": "get"
},
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "7abd99c5-c778-454f-88d1-7d440e4a9bdd",
"name": "Logic Engine",
"type": "n8n-nodes-base.code",
"position": [
-1200,
176
],
"parameters": {
"jsCode": "const triggerData = $('Telegram Trigger').first().json;\nlet dbData = $('Supabase: Get Session').first().json || {};\n\nif (Array.isArray(dbData)) dbData = dbData[0] || {};\n\nlet chatId = triggerData.message?.chat?.id?.toString() || triggerData.callback_query?.message?.chat?.id?.toString();\nlet msg = triggerData.message?.text || triggerData.callback_query?.data || \"\";\n\nif (!chatId) return { json: { stop: true } };\n\nconst recordExists = !!(dbData && dbData.chat_id);\n\nlet currentState = dbData.state || \"START\";\nlet mode = dbData.mode || \"\";\nlet p1 = dbData.p1_data || \"\";\nlet p2 = dbData.p2_data || \"\";\n\nlet reply = \"I'm processing that... one moment.\";\nlet nextState = currentState;\nlet routeChat = false;\nlet isComplete = false;\n\nif (msg.toLowerCase().includes(\"/start\")) {\n currentState = \"START\";\n}\n\nswitch (currentState) {\n case \"START\":\n reply = \"Welcome! Choose: \ud83d\udd2e <b>Chart</b> or \ud83d\udc9e <b>Synastry</b>?\";\n nextState = \"AWAITING_CHOICE\";\n break;\n\n case \"AWAITING_CHOICE\":\n if (msg.toLowerCase().includes(\"natal\") || msg.toLowerCase().includes(\"chart\")) {\n mode = \"natal\";\n reply = \"\ud83d\udd2e <b>Personal Chart Mode</b>\\nSend your birth data (City, DD/MM/YYYY HH:mm):\";\n nextState = \"AWAITING_NATAL_DATA\";\n } else if (msg.toLowerCase().includes(\"synastry\")) {\n mode = \"synastry\";\n reply = \"\ud83d\udc9e <b>Synastry Mode</b>\\nSend Person 1 data (City, DD/MM/YYYY HH:mm):\";\n nextState = \"AWAITING_P1\";\n } else {\n reply = \"Please choose: \ud83d\udd2e <b>Chart</b> or \ud83d\udc9e <b>Synastry</b>?\";\n nextState = \"AWAITING_CHOICE\";\n }\n break;\n\n case \"AWAITING_NATAL_DATA\":\n p1 = msg;\n reply = \"\u2728 Calculating your personal natal alignment... \u2728\";\n nextState = \"CHAT\";\n isComplete = true;\n break;\n\n case \"AWAITING_P1\":\n p1 = msg;\n reply = \"\u2705 Got Person 1. Now send <b>Person 2 data</b> (City, DD/MM/YYYY HH:mm):\";\n nextState = \"AWAITING_P2\";\n break;\n\n case \"AWAITING_P2\":\n p2 = msg;\n reply = \"\ud83c\udf0c Comparing soul dynamics... \u2728\";\n nextState = \"CHAT\";\n isComplete = true;\n break;\n\n case \"CHAT\":\n routeChat = true;\n nextState = \"CHAT\";\n break;\n \n default:\n reply = \"Welcome! Choose: \ud83d\udd2e <b>Chart</b> or \ud83d\udc9e <b>Synastry</b>?\";\n nextState = \"AWAITING_CHOICE\";\n break;\n}\n\nreturn {\n json: {\n chatId,\n reply,\n nextState,\n recordExists,\n mode,\n p1_data: p1,\n p2_data: p2,\n routeChat,\n isComplete,\n userQuery: msg\n }\n};"
},
"typeVersion": 2
},
{
"id": "e534839f-a1fc-4477-8e8c-c68e59851fed",
"name": "Record Exists?",
"type": "n8n-nodes-base.if",
"position": [
-976,
176
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "check_exists",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.recordExists }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "bfaae5ea-69ad-4c6e-acc3-d28613a55aae",
"name": "Supabase: Update Session",
"type": "n8n-nodes-base.supabase",
"position": [
-768,
64
],
"parameters": {
"filters": {
"conditions": [
{
"keyName": "chat_id",
"keyValue": "={{ $json.chatId }}",
"condition": "eq"
}
]
},
"tableId": "sessions",
"fieldsUi": {
"fieldValues": [
{
"fieldId": "state",
"fieldValue": "={{ $json.nextState }}"
},
{
"fieldId": "mode",
"fieldValue": "={{ $json.mode }}"
},
{
"fieldId": "p1_data",
"fieldValue": "={{ $json.p1_data }}"
},
{
"fieldId": "p2_data",
"fieldValue": "={{ $json.p2_data }}"
}
]
},
"operation": "update"
},
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "1588cc7e-7c01-467f-a3b2-8bb26e19d79e",
"name": "Supabase: Insert Session",
"type": "n8n-nodes-base.supabase",
"position": [
-768,
288
],
"parameters": {
"tableId": "sessions",
"fieldsUi": {
"fieldValues": [
{
"fieldId": "chat_id",
"fieldValue": "={{ $json.chatId }}"
},
{
"fieldId": "state",
"fieldValue": "={{ $json.nextState }}"
},
{
"fieldId": "mode",
"fieldValue": "={{ $json.mode }}"
},
{
"fieldId": "p1_data",
"fieldValue": "={{ $json.p1_data }}"
},
{
"fieldId": "p2_data",
"fieldValue": "={{ $json.p2_data }}"
}
]
}
},
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "d6c8a9b9-2905-45b2-b5e6-0871f81f85d7",
"name": "Is Pure Chat?",
"type": "n8n-nodes-base.if",
"position": [
-496,
176
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "check_rag",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $('Logic Engine').item.json.routeChat }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "1f889e97-35b9-4d45-b073-9a0312d5f128",
"name": "Calculate Reading Now?",
"type": "n8n-nodes-base.if",
"position": [
-288,
272
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "is_complete",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $('Logic Engine').item.json.isComplete }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "10b47fcb-5631-4973-808e-e81582d411ae",
"name": "Parse Intake Data",
"type": "n8n-nodes-base.code",
"position": [
-80,
384
],
"parameters": {
"jsCode": "const engineData = $('Logic Engine').item.json;\nconst p1Raw = engineData.p1_data || \"\";\nconst p2Raw = engineData.p2_data || \"\";\n\nfunction parseInput(str, personName) {\n const parts = str.split(',');\n const city = parts[0] ? parts[0].trim() : \"Tbilisi\";\n const timedata = parts[1] ? parts[1].trim().split(' ') : [];\n const datePart = timedata[0] || \"19/05/2026\";\n const timePart = timedata[1] || \"12:00\";\n \n const [day, month, year] = datePart.split('/').map(Number);\n const [hour, min] = timePart.split(':').map(Number);\n \n return { person: personName, city, day: day||19, month: month||5, year: year||2026, hour: hour||12, min: min||0 };\n}\n\nconst items = [];\nif (p1Raw) items.push({ json: parseInput(p1Raw, \"p1\") });\nif (p2Raw) items.push({ json: parseInput(p2Raw, \"p2\") });\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "853632e0-d668-49ce-978c-ac4a2804c69b",
"name": "Get Coordinates",
"type": "n8n-nodes-base.httpRequest",
"position": [
112,
384
],
"parameters": {
"url": "https://nominatim.openstreetmap.org/search",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "format",
"value": "json"
},
{
"name": "q",
"value": "={{ $json.city }}"
}
]
}
},
"typeVersion": 4.1
},
{
"id": "99cdc5f9-d14b-422d-aa3c-8fec19d25c57",
"name": "Get Timezone Offset",
"type": "n8n-nodes-base.httpRequest",
"position": [
320,
384
],
"parameters": {
"url": "https://secure.geonames.org/timezoneJSON",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "lat",
"value": "={{ $json.lat }}"
},
{
"name": "lng",
"value": "={{ $json.lon }}"
},
{
"name": "username",
"value": "sally.tkhilaishvili"
}
]
}
},
"typeVersion": 4.1
},
{
"id": "bacb26b5-00e0-4eae-b3a0-661fc787bdb9",
"name": "Astrology API Engine",
"type": "n8n-nodes-base.httpRequest",
"position": [
736,
384
],
"parameters": {
"url": "={{ $json.apiUrl }}",
"method": "POST",
"options": {},
"jsonBody": "={{ $json.payload }}",
"sendBody": true,
"sendQuery": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"queryParameters": {
"parameters": [
{}
]
}
},
"credentials": {
"httpBasicAuth": {
"name": "<your credential>"
},
"httpBearerAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.1
},
{
"id": "035e7f17-13c1-42db-a10f-cc4a90bca7df",
"name": "Download RAG JSON",
"type": "n8n-nodes-base.googleDrive",
"position": [
32,
144
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "list",
"value": "15q08k0TPfRboMZmTuUaklpv0yWfP7jl6",
"cachedResultUrl": "https://drive.google.com/file/d/15q08k0TPfRboMZmTuUaklpv0yWfP7jl6/view?usp=drivesdk",
"cachedResultName": "synastry.json"
},
"options": {},
"operation": "download"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "330d3098-9698-4a68-b380-8713e88a71a9",
"name": "Final Similarity Filter",
"type": "n8n-nodes-base.code",
"position": [
336,
144
],
"parameters": {
"jsCode": "const rawSnippets = $json.snippets || [];\nlet processedRagContext = \"No additional manuscript contexts retrieved.\";\n\nif (Array.isArray(rawSnippets) && rawSnippets.length > 0) {\n processedRagContext = rawSnippets.join('\\n');\n} else if (typeof rawSnippets === 'string') {\n processedRagContext = rawSnippets;\n}\n\nreturn {\n json: {\n processedRagContext\n }\n};"
},
"typeVersion": 2
},
{
"id": "db97358b-3c55-47b7-ac6a-c50d26a9272f",
"name": "Supabase: Log Reading History",
"type": "n8n-nodes-base.supabase",
"position": [
2000,
384
],
"parameters": {
"tableId": "astrology_readings",
"fieldsUi": {
"fieldValues": [
{
"fieldId": "chat_id",
"fieldValue": "={{ $('Logic Engine').item.json.chatId }}"
},
{
"fieldId": "mode",
"fieldValue": "={{ $('Logic Engine').item.json.mode }}"
},
{
"fieldId": "p1_data",
"fieldValue": "={{ $('Logic Engine').item.json.p1_data }}"
},
{
"fieldId": "p2_data",
"fieldValue": "={{ $('Logic Engine').item.json.p2_data }}"
}
]
}
},
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "7c33f607-ca1f-4987-a028-c0141a555cdc",
"name": "Telegram Bot Response",
"type": "n8n-nodes-base.telegram",
"position": [
2192,
384
],
"parameters": {
"text": "={{ $('Code in JavaScript').item.json.text }}",
"chatId": "={{ $('Logic Engine').item.json.chatId }}",
"additionalFields": {
"parse_mode": "HTML"
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "fa1cd9b8-f508-42dc-840f-8c91f6b08010",
"name": "Send a text message",
"type": "n8n-nodes-base.telegram",
"position": [
0,
0
],
"parameters": {
"text": "={{ $('Logic Engine').item.json.reply }}",
"chatId": "={{ $('Logic Engine').item.json.chatId }}",
"additionalFields": {
"parse_mode": "HTML"
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "75c1db7a-0346-4a73-a9ff-c8f20d2554d9",
"name": "Code in JavaScript",
"type": "n8n-nodes-base.code",
"position": [
1808,
384
],
"parameters": {
"jsCode": "try {\n // Looks for 'output' or fallback 'text' from the incoming node data\n let rawOutput = $input.first().json.output || $input.first().json.text || \"\";\n return [{ json: { text: rawOutput } }];\n} catch (e) {\n return [{ json: { text: \"\u26a0\ufe0f <b>System Error:</b> Breakdown framing contextual response data.\" } }];\n}"
},
"typeVersion": 2
},
{
"id": "955d3c22-3d63-462c-848d-a1e07cac45f7",
"name": "Prepare input for natal/synastry calculation",
"type": "n8n-nodes-base.code",
"position": [
544,
384
],
"parameters": {
"jsCode": "// 1. Fetch data safely\nconst logicEngine = $('Logic Engine').first()?.json || {};\n\n// If we are just chatting, skip deep lookups to prevent execution crashes\nif (logicEngine.routeChat && !logicEngine.isComplete) {\n return [{\n json: {\n apiUrl: \"https://json.astrologyapi.com/v1/western_horoscope\",\n payload: {},\n isChatRoute: true\n }\n }];\n}\n\nlet parsedIntake = [];\nlet geoCoordinates = [];\nlet tzOffsets = [];\n\n// Use try/catch blocks to prevent crashes if nodes weren't executed\ntry { parsedIntake = $('Parse Intake Data').all().map(item => item.json || {}); } catch(e) {}\ntry { geoCoordinates = $('Get Coordinates').all().map(item => item.json || {}); } catch(e) {}\ntry { tzOffsets = $('Get Timezone Offset').all().map(item => item.json || {}); } catch(e) {}\n\nconst isSynastry = !!logicEngine.p2_data;\nconst targetUrl = isSynastry \n ? 'https://json.astrologyapi.com/v1/synastry_horoscope' \n : 'https://json.astrologyapi.com/v1/western_horoscope';\n\n// 2. Robust Parser\nfunction getPersonData(index) {\n const intake = parsedIntake[index] || {};\n const geo = geoCoordinates[index] || {};\n const tz = tzOffsets[index] || {};\n\n return {\n day: parseInt(intake.day, 10) || 19,\n month: parseInt(intake.month, 10) || 5,\n year: parseInt(intake.year, 10) || 2026,\n hour: parseInt(intake.hour, 10) || 12,\n min: parseInt(intake.min, 10) || 0,\n latitude: parseFloat(geo.lat || geo[0]?.lat || 41.7151),\n longitude: parseFloat(geo.lon || geo.lng || geo[0]?.lon || 44.8271),\n timezone: parseFloat(tz.gmtOffset || 4.0)\n };\n}\n\n// 3. Construct Payload (Adjusted for flat structure)\nlet finalPayload = {};\n\nif (isSynastry) {\n const p1 = getPersonData(0);\n const p2 = getPersonData(1);\n \n finalPayload = {\n p_day: p1.day, p_month: p1.month, p_year: p1.year,\n p_hour: p1.hour, p_min: p1.min, p_lat: p1.latitude, \n p_lon: p1.longitude, p_tzone: p1.timezone,\n \n s_day: p2.day, s_month: p2.month, s_year: p2.year,\n s_hour: p2.hour, s_min: p2.min, s_lat: p2.latitude, \n s_lon: p2.longitude, s_tzone: p2.timezone\n };\n} else {\n const p1 = getPersonData(0);\n finalPayload = {\n day: p1.day, month: p1.month, year: p1.year,\n hour: p1.hour, min: p1.min, lat: p1.latitude, \n lon: p1.longitude, tzone: p1.timezone\n };\n}\n\n// 4. Return\nreturn [{\n json: {\n apiUrl: targetUrl,\n payload: finalPayload,\n isChatRoute: false,\n chatId: $('Record Exists?').first().json.chatId\n }\n}];"
},
"typeVersion": 2
},
{
"id": "49d864a7-41c2-4145-8adf-c79d8957800e",
"name": "Convert service output to context",
"type": "n8n-nodes-base.code",
"position": [
960,
384
],
"parameters": {
"jsCode": "const logicEngineData = $('Logic Engine').first()?.json || {};\nconst userPrompt = logicEngineData.userQuery || \"Analyze chart\"; \n\nlet diagnosticStr = \"NO_LIVE_CHART_CALCULATED\";\nlet processedRagContext = \"No additional manuscript contexts retrieved.\";\n\n// 1. Check for LIVE calculations safely using try/catch\ntry {\n const apiData = $('Astrology API Engine').first()?.json;\n \n // Only parse if the node actually returned real planetary positions\n if (apiData && (apiData.planets || apiData.first)) {\n diagnosticStr = parseAstrologyPayload(apiData);\n }\n} catch (e) {\n // Node wasn't executed or was bypassed, diagnosticStr remains 'NO_LIVE_CHART_CALCULATED'\n}\n\n// 2. Try to fetch RAG Context cleanly\ntry {\n const ragNode = $('Final Similarity Filter').first()?.json;\n if (ragNode && ragNode.processedRagContext) {\n processedRagContext = ragNode.processedRagContext;\n }\n} catch (e) {}\n\n// Helper function to stringify planetary data cleanly\nfunction parseAstrologyPayload(apiData) {\n const formatPlanets = (planetList, label) => {\n return planetList.map(p => `${label}: ${p.name} in ${p.sign} (House ${p.house})`).join(', ');\n };\n if (apiData.first && apiData.second) {\n let str = `${formatPlanets(apiData.first, \"Person 1\")} | ${formatPlanets(apiData.second, \"Person 2\")}`;\n if (apiData.aspects) {\n const aspects = apiData.aspects.map(a => `${a.aspecting_planet} ${a.type} ${a.aspected_planet} (Orb: ${a.orb}\u00b0)`).join(', ');\n str += ` | Aspects: ${aspects}`;\n }\n return str;\n } else if (apiData.planets) {\n return apiData.planets.map(p => `${p.name} in ${p.sign} (House ${p.house})`).join(', ');\n }\n return \"NO_LIVE_CHART_CALCULATED\";\n}\n\nreturn {\n json: {\n chatInput: userPrompt,\n driveContext: processedRagContext,\n astrologyData: diagnosticStr,\n isCalculationRoute: (diagnosticStr !== \"NO_LIVE_CHART_CALCULATED\"),\n sessionId :$('Record Exists?').first().json.chatId \n }\n};"
},
"typeVersion": 2
},
{
"id": "2ceeeefb-a02e-4c90-979e-46c54e9f4364",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1408,
384
],
"parameters": {
"options": {
"systemMessage": "=You are a precision-focused Astrological Agent interacting with a user over a continuous chat interface.\n\nCURRENT LIVE CHART DATA SOURCE:\n{{ $json.sanitizedAstrologyData }}\n\nCRITICAL EXECUTION PROTOCOL:\n- Check the 'CURRENT LIVE CHART DATA SOURCE' block above. If metrics are present, you must synthesize BOTH the planetary placements and the critical aspects to build your analysis.\n- If it says \"NO_LIVE_CALCULATION_THIS_TURN_USE_HISTORY_MEMORY\", scan your internal chat history memory window. Look at the profile parameters built earlier in this conversation thread to answer their new questions.\n- You are STRICTLY FORBIDDEN from giving generic textbook listings or telling users what to \"look for.\" Synthesize their specific data placements directly.\n\nCRITICAL TELEGRAM DELIVERY & LENGTH CONSTRAINT:\n- The delivery platform has a strict technical ceiling. Your entire generated response MUST be brief, concise, and under 3,200 characters total (including all spaces, markdown text, and HTML tags). \n- Avoid wordy preambles, introductory fluff, or long conclusions. Get straight to the analysis of the chart positions so the response easily fits in a single message payload.\n\nCRITICAL FORMATTING SANITIZATION LAWS:\n1. NEVER output raw \"\\n\" text strings. Use true keyboard carriage returns to separate paragraphs.\n2. DO NOT use HTML <br> tags under any circumstances.\n3. Use HTML bold tags for core thematic anchors (e.g., <b>\ud83e\ude90 The Pluto-Midheaven Conjunction</b>).\n4. Use code style formatting tags to isolate numerical values, angles, or houses (e.g., <code>Pluto Conjunction Midheaven (Orb: 0.73\u00b0)</code>).\n5. Prefix your main content section titles with a single cosmic emoji (\u2728, \ud83e\ude90, \ud83c\udf19)."
}
},
"typeVersion": 3.1
},
{
"id": "5c7c1068-e461-4d2e-b4a2-8fe8060d9692",
"name": "Google Gemini Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
1408,
608
],
"parameters": {
"options": {},
"modelName": "models/gemini-3.1-flash-lite"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "fe92d63b-1e30-41ba-b2d6-0963e85dfa6b",
"name": "Simple Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
1504,
608
],
"parameters": {},
"typeVersion": 1.4
},
{
"id": "a10e6051-2254-488c-886a-25d2a9f955c4",
"name": "Prepare data for agent",
"type": "n8n-nodes-base.code",
"position": [
1168,
384
],
"parameters": {
"jsCode": "let isSynastryRoute = false;\nlet natalPlacements = [];\nlet p1Placements = [];\nlet p2Placements = [];\nlet consolidatedAspects = [];\nlet formattedText = \"NO_LIVE_CALCULATION_THIS_TURN_USE_HISTORY_MEMORY\";\nlet activeSessionId = \"\";\nlet activeChatInput = \"\";\n\n// Native, safe n8n global helper input retrieval\nconst items = $input.all();\n\nif (items && items.length > 0) {\n items.forEach(item => {\n const data = item.json;\n if (!data) return;\n\n // Safely trap passthrough parameters\n if (data.sessionId) activeSessionId = data.sessionId;\n if (data.chatInput) activeChatInput = data.chatInput;\n if (!activeSessionId && activeChatInput) activeSessionId = activeChatInput;\n\n // --- 1. PARSE OUT RAW PAYLOAD PAYLOAD STRINGS ---\n if (data.astrologyData && typeof data.astrologyData === 'string') {\n if (data.astrologyData.includes('Person 2:') || data.astrologyData.includes('P2 ')) {\n isSynastryRoute = true;\n }\n \n let parts = data.astrologyData.split('|');\n parts.forEach(part => {\n let elements = part.split(',').map(e => e.trim());\n elements.forEach(el => {\n if (el.startsWith('Person 1:') || el.startsWith('P1 ')) {\n p1Placements.push(`\u2022 ${el.replace(/Person 1:\\s*|P1\\s*/i, '')}`);\n } else if (el.startsWith('Person 2:') || el.startsWith('P2 ')) {\n p2Placements.push(`\u2022 ${el.replace(/Person 2:\\s*|P2\\s*/i, '')}`);\n } else if (el.length > 0) {\n natalPlacements.push(`\u2022 ${el}`);\n }\n });\n });\n }\n \n // --- 2. BACKUP: SCENARIO ARRAY PROCESSING ---\n else if ((data.first && data.first.length > 0) || (data.second && data.second.length > 0)) {\n isSynastryRoute = true;\n if (Array.isArray(data.first)) {\n data.first.forEach(p => p1Placements.push(`\u2022 ${p.name}: ${p.sign} (${p.house}H)`));\n }\n if (Array.isArray(data.second)) {\n data.second.forEach(p => p2Placements.push(`\u2022 ${p.name}: ${p.sign} (${p.house}H)`));\n }\n }\n else if (data.planets && Array.isArray(data.planets)) {\n data.planets.forEach(p => natalPlacements.push(`\u2022 ${p.name}: ${p.sign} (${p.house}H)`));\n }\n\n // --- 3. CAPTURE AUTOMATED ASPECTS (IF AVAILABLE) ---\n let targets = data.aspects || data;\n if (Array.isArray(targets)) {\n targets.forEach(a => {\n if (a.aspecting_planet && a.aspected_planet) {\n let orbVal = parseFloat(a.orb) || 0;\n consolidatedAspects.push({\n string: `<code>${a.aspecting_planet} ${a.type} ${a.aspected_planet} (Orb: ${orbVal.toFixed(2)}\u00b0)</code>`,\n orb: orbVal\n });\n }\n });\n }\n });\n\n // Sort aspects by exact mathematical closeness\n consolidatedAspects.sort((a, b) => a.orb - b.orb);\n let aspectStrings = consolidatedAspects.map(a => a.string);\n\n // --- 4. ASSEMBLE STRUCTURAL WORKFLOW OUTPUT ---\n let output = `[LIVE DATA PACKET RATIFIED]\\n`;\n output += `ROUTE_DETECTED: ${isSynastryRoute ? 'SYNASTRY_COMPATIBILITY' : 'NATAL_BLUEPRINT'}\\n\\n`;\n\n if (isSynastryRoute) {\n output += `### PROFILE 1 CORE PLACEMENTS (HOME BASELINE)\\n` + (p1Placements.join('\\n') || 'No P1 Placements Found') + `\\n\\n`;\n output += `### PROFILE 2 CORE PLACEMENTS (INCOMING ENERGY)\\n` + (p2Placements.join('\\n') || 'No P2 Placements Found') + `\\n\\n`;\n output += `### CRITICAL CROSS-CHART GEOMETRIC INTERSECTIONS (SORTED BY URGENCY)\\n`;\n output += aspectStrings.length > 0 ? aspectStrings.join('\\n') : 'No tight inter-chart alignments under 4.0\u00b0. Focus heavily on the House Overlays above.';\n } else {\n output += `### INDIVIDUAL NATAL PLACEMENTS\\n` + (natalPlacements.join('\\n') || 'No Natal Placements Found') + `\\n\\n`;\n output += `### INTERNAL ASPECT GEOMETRY (SORTED BY FORCE INTENSITY)\\n`;\n output += aspectStrings.length > 0 ? aspectStrings.join('\\n') : 'No major planetary aspects active.';\n }\n\n formattedText = output;\n}\n\nreturn [\n {\n json: {\n sessionId: activeSessionId,\n chatInput: activeChatInput,\n sanitizedAstrologyData: formattedText\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "5f951300-72fe-463d-83f6-ae7648a7dd5d",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2160,
-128
],
"parameters": {
"width": 432,
"height": 688,
"content": "\ud83c\udf0c Unified AstroBot Engine v3\nHow it works\nTrigger: The workflow captures incoming Telegram messages or button callback queries.\n\nSession Retrieval: It fetches the conversation state from Supabase using the user's unique chat_id.\n\nLogic Engine: A centralized JavaScript routing engine processes user inputs, handles state changes (START, AWAITING_CHOICE, CHAT), and updates the session data.\n\nData Enrichment & RAG: If the data collection for a reading is complete, the engine coordinates background calls to parse location strings, grab coordinates via Nominatim, determine timezone details, and fetch context files from Google Drive.\n\nResponse Delivery: The workflow dynamically routes the execution path to either send operational messages or full AI/astrological readings back to the user via Telegram.\n\nSetup steps\n[ ] Add your Telegram Bot API credentials to the trigger and reply nodes.\n\n[ ] Connect your Supabase credentials and verify that your sessions table matches the core fields (chat_id, state, mode, p1_data, p2_data).\n\n[ ] Authenticate your Google Drive node to allow the engine to access your reference data (synastry.json).\n\n[ ] Verify your Astro API / GeoNames credentials for geo-data enrichment.\n\nCustomization\nYou can expand the switch-case conditions inside the Logic Engine node to handle advanced interactive states or modify the Parse Intake Data structure to accept localized date-time formatting patterns.\n"
},
"typeVersion": 1
},
{
"id": "7fd94a2f-9442-44e6-9e55-a4ac18028741",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1696,
16
],
"parameters": {
"color": 7,
"width": 1344,
"height": 448,
"content": "## Work on chat's input output format and send user's feedback to superbase database\n\n"
},
"typeVersion": 1
},
{
"id": "b6943ad9-4b3a-4590-acdf-30d0b8ad0d5c",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-96,
-112
],
"parameters": {
"color": 7,
"width": 624,
"height": 432,
"content": "## Reitrive file from storage and analize how similar it is to user's request\n"
},
"typeVersion": 1
},
{
"id": "3e2853b3-f147-4ef5-81d5-2e72a9e954e3",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-112,
320
],
"parameters": {
"color": 7,
"width": 1424,
"height": 272,
"content": "## After analyzing user request, get coordinates, timezones and use user input to calulate natal chart by external api, and format output to send parsed data to AI Agent\n\n"
},
"typeVersion": 1
},
{
"id": "afff7fa0-2e95-4d0a-9a43-564ef3ec8998",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1360,
272
],
"parameters": {
"color": 7,
"width": 1008,
"height": 560,
"content": "## Get AI output based on user prompt, RAG restul and user's request and send them to superbase database and to telegram message bot."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "e3327ffd-a010-4a23-8fea-dfd09afbe68a",
"connections": {
"AI Agent": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Logic Engine": {
"main": [
[
{
"node": "Record Exists?",
"type": "main",
"index": 0
}
]
]
},
"Is Pure Chat?": {
"main": [
[
{
"node": "Download RAG JSON",
"type": "main",
"index": 0
}
],
[
{
"node": "Calculate Reading Now?",
"type": "main",
"index": 0
}
]
]
},
"Simple Memory": {
"ai_memory": [
[
{
"node": "AI Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Record Exists?": {
"main": [
[
{
"node": "Supabase: Update Session",
"type": "main",
"index": 0
}
],
[
{
"node": "Supabase: Insert Session",
"type": "main",
"index": 0
}
]
]
},
"Get Coordinates": {
"main": [
[
{
"node": "Get Timezone Offset",
"type": "main",
"index": 0
}
]
]
},
"Telegram Trigger": {
"main": [
[
{
"node": "Supabase: Get Session",
"type": "main",
"index": 0
}
]
]
},
"Download RAG JSON": {
"main": [
[
{
"node": "Final Similarity Filter",
"type": "main",
"index": 0
}
]
]
},
"Parse Intake Data": {
"main": [
[
{
"node": "Get Coordinates",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "Supabase: Log Reading History",
"type": "main",
"index": 0
}
]
]
},
"Get Timezone Offset": {
"main": [
[
{
"node": "Prepare input for natal/synastry calculation",
"type": "main",
"index": 0
}
]
]
},
"Astrology API Engine": {
"main": [
[
{
"node": "Convert service output to context",
"type": "main",
"index": 0
}
]
]
},
"Supabase: Get Session": {
"main": [
[
{
"node": "Logic Engine",
"type": "main",
"index": 0
}
]
]
},
"Calculate Reading Now?": {
"main": [
[
{
"node": "Parse Intake Data",
"type": "main",
"index": 0
}
],
[
{
"node": "Send a text message",
"type": "main",
"index": 0
}
]
]
},
"Prepare data for agent": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Final Similarity Filter": {
"main": [
[
{
"node": "Convert service output to context",
"type": "main",
"index": 0
}
]
]
},
"Supabase: Insert Session": {
"main": [
[
{
"node": "Is Pure Chat?",
"type": "main",
"index": 0
}
]
]
},
"Supabase: Update Session": {
"main": [
[
{
"node": "Is Pure Chat?",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model1": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Supabase: Log Reading History": {
"main": [
[
{
"node": "Telegram Bot Response",
"type": "main",
"index": 0
}
]
]
},
"Convert service output to context": {
"main": [
[
{
"node": "Prepare data for agent",
"type": "main",
"index": 0
}
]
]
},
"Prepare input for natal/synastry calculation": {
"main": [
[
{
"node": "Astrology API Engine",
"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.
googleDriveOAuth2ApigooglePalmApihttpBasicAuthhttpBearerAuthsupabaseApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow powers a Telegram astrology bot that manages chat sessions in Supabase, calculates natal or synastry charts via AstrologyAPI using location and timezone lookups, and uses Google Gemini (via an n8n AI Agent) plus reference text from Google Drive to craft responses…
Source: https://n8n.io/workflows/15944/ — 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.
Arvifund - Supabase. Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.
Arvifund - Supabase (Fixed v2). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.
Arvifund - Supabase (Fixed v4). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.
Arvifund - Supabase (Fixed v3). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.
Inbox Guardian. Uses gmailTrigger, lmChatOpenAi, agent, textClassifier. Event-driven trigger; 66 nodes.