{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "e22ea54c-d92b-455d-80e9-5d82c9fd5c7f",
      "name": "Groq Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "position": [
        -4640,
        32
      ],
      "parameters": {
        "model": "openai/gpt-oss-120b",
        "options": {}
      },
      "credentials": {
        "groqApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "131ad1c0-bb33-4ed1-b07e-43439e513c37",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -4256,
        32
      ],
      "parameters": {
        "autoFix": true,
        "jsonSchemaExample": "{\n  \"intent\": \"search\",\n  \"keyword\": \"Webhook signature verification\",\n  \"platforms\": \"all\",\n  \"reddit_sort\": \"top\",\n  \"reddit_time\": \"year\",\n  \"n8ncom_sort\": \"likes\",\n  \"n8ncom_time\": \"after:2025-01-01\",\n  \"limit\": 10,\n  \"page\": 1,\n  \"link_url\": \"https://nguyenthieutoan.com\",\n  \"language\": \"vi\",\n  \"confidence\": 0.95\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "e6c99b66-658d-4643-b560-06b483199bd6",
      "name": "MongoDB Chat Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryMongoDbChat",
      "position": [
        -4448,
        16
      ],
      "parameters": {
        "sessionKey": "={{ $('Telegram Trigger - User Message').item.json.message.from.id }}",
        "databaseName": "n8n_admin_chat",
        "sessionIdType": "customKey",
        "collectionName": "n8n_forum_update_chat"
      },
      "credentials": {
        "mongoDb": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "439200ab-1e43-4458-aadd-30fe644462fc",
      "name": "If Reddit?",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2912,
        304
      ],
      "parameters": {
        "url": "={{ $json.output.link_url }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "157b9d76-9081-4825-ad4d-52176c371cc6",
      "name": "Comment",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2464,
        160
      ],
      "parameters": {
        "url": "=https://www.reddit.com{{ $json.comment_url }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "f35d422d-6bf1-43ad-a414-3989c0abe075",
      "name": "Get Post Content",
      "type": "n8n-nodes-base.html",
      "position": [
        -2688,
        304
      ],
      "parameters": {
        "options": {},
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "post_title",
              "cssSelector": "h1"
            },
            {
              "key": "post_author",
              "cssSelector": "a.author-name"
            },
            {
              "key": "post_content",
              "cssSelector": "div[slot='text-body']"
            },
            {
              "key": "post_upvotes",
              "attribute": "score",
              "cssSelector": "shreddit-post",
              "returnValue": "attribute"
            },
            {
              "key": "commentCount",
              "attribute": "comment-count",
              "cssSelector": "shreddit-post",
              "returnValue": "attribute"
            },
            {
              "key": "postTime",
              "attribute": "ts",
              "cssSelector": "faceplate-timeago",
              "returnValue": "attribute"
            },
            {
              "key": "flair",
              "cssSelector": "shreddit-post-flair .flair-content"
            },
            {
              "key": "comment_url",
              "attribute": "src",
              "cssSelector": "faceplate-partial[src^=\"/svc/shreddit/comments/\"]",
              "returnValue": "attribute"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d2c77457-4b74-43fc-8787-6449e7d4fbf7",
      "name": "Get Comment",
      "type": "n8n-nodes-base.html",
      "position": [
        -2240,
        160
      ],
      "parameters": {
        "options": {
          "trimValues": true,
          "cleanUpText": true
        },
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "comment_author",
              "attribute": "author",
              "cssSelector": "shreddit-comment",
              "returnArray": true,
              "returnValue": "attribute"
            },
            {
              "key": "comment_content",
              "cssSelector": "shreddit-comment div[slot='comment']",
              "returnArray": true,
              "returnValue": "html"
            },
            {
              "key": "comment_upvotes",
              "attribute": "score",
              "cssSelector": "shreddit-comment",
              "returnArray": true,
              "returnValue": "attribute"
            },
            {
              "key": "comment_level",
              "attribute": "depth",
              "cssSelector": "shreddit-comment",
              "returnArray": true,
              "returnValue": "attribute"
            },
            {
              "key": "comment_id",
              "attribute": "thingid",
              "cssSelector": "shreddit-comment",
              "returnArray": true,
              "returnValue": "attribute"
            },
            {
              "key": "parent_id",
              "attribute": "parentid",
              "cssSelector": "shreddit-comment",
              "returnArray": true,
              "returnValue": "attribute"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "22f67ac5-02d7-41ed-840b-904a537145c8",
      "name": "If n8n Community?",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2912,
        496
      ],
      "parameters": {
        "url": "={{ $('Detect User Intent').item.json.output.link_url }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "a4e59ef0-159e-4099-bfca-636af4dfc8d9",
      "name": "Get Topic Content",
      "type": "n8n-nodes-base.html",
      "position": [
        -2464,
        400
      ],
      "parameters": {
        "options": {},
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "topic_title",
              "cssSelector": "#topic-title a"
            },
            {
              "key": "category",
              "cssSelector": ".topic-category .category-name"
            },
            {
              "key": "original_poster",
              "cssSelector": "#post_1 .creator span[itemprop='name']"
            },
            {
              "key": "original_post_content",
              "cssSelector": "#post_1 div.post[itemprop='text']\t"
            },
            {
              "key": "original_post_likes",
              "cssSelector": "#post_1 .post-likes"
            },
            {
              "key": "original_post_time",
              "attribute": "datetime",
              "cssSelector": "#post_1 time.post-time",
              "returnValue": "attribute"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "838171e0-c801-4ccf-bcb4-b4501f6c58a3",
      "name": "Get Comment1",
      "type": "n8n-nodes-base.html",
      "position": [
        -2688,
        592
      ],
      "parameters": {
        "options": {},
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "comment_author",
              "cssSelector": "div.crawler-post[itemprop='comment'] span[itemprop='name']",
              "returnArray": true
            },
            {
              "key": "comment_content",
              "cssSelector": "div.crawler-post[itemprop='comment'] div.post[itemprop='text']",
              "returnArray": true,
              "returnValue": "html"
            },
            {
              "key": "comment_likes",
              "cssSelector": "div.crawler-post[itemprop='comment'] .post-likes",
              "returnArray": true
            },
            {
              "key": "comment_post_number",
              "cssSelector": "div.crawler-post[itemprop='comment'] span[itemprop='position']",
              "returnArray": true
            },
            {
              "key": "comment_time",
              "attribute": "datetime",
              "cssSelector": "div.crawler-post[itemprop='comment'] time.post-time",
              "returnArray": true,
              "returnValue": "attribute"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "e3aa4967-b214-4312-98fe-51586ea06823",
      "name": "Platform?",
      "type": "n8n-nodes-base.switch",
      "position": [
        -3056,
        -928
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "39188959-a286-43a4-b9b7-c8cd2667e225",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.output.platforms }}",
                    "rightValue": "=all"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "9cc13c1f-e903-4c51-89de-ebd290888cef",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.output.platforms }}",
                    "rightValue": "reddit"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "940f40a0-4cb0-41a7-8c5e-7e553bc1acf0",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.output.platforms }}",
                    "rightValue": "n8ncom"
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "f78f3d7c-c2a2-4bb0-a828-ea14a3efb5d4",
      "name": "MongoDB Chat Memory1",
      "type": "@n8n/n8n-nodes-langchain.memoryMongoDbChat",
      "position": [
        -1136,
        -32
      ],
      "parameters": {
        "sessionKey": "={{ $('Telegram Trigger - User Message').item.json.message.from.id }}",
        "databaseName": "n8n_admin_chat",
        "sessionIdType": "customKey",
        "collectionName": "n8n_forum_update_chat"
      },
      "credentials": {
        "mongoDb": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "349d9d39-197c-498c-b89e-43a760a48905",
      "name": "Merge Sources1",
      "type": "n8n-nodes-base.merge",
      "position": [
        -4224,
        -1168
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combinationMode": "mergeByPosition"
      },
      "typeVersion": 2
    },
    {
      "id": "974347e4-9cc5-401c-aeee-061f7dc3d1b2",
      "name": "Get Only Search Result1",
      "type": "n8n-nodes-base.code",
      "position": [
        -4448,
        -1184
      ],
      "parameters": {
        "jsCode": "// L\u1ea5y d\u1eef li\u1ec7u Reddit Fetch t\u1eeb item \u0111\u1ea7u v\u00e0o\nconst redditData = items[0].json.data.children;\n\n// T\u1ea1o array m\u1edbi g\u1ed3m c\u00e1c th\u00f4ng tin chi ti\u1ebft h\u01a1n cho m\u1ed7i k\u1ebft qu\u1ea3\nconst detailedResults = redditData.map(item => {\n  const data = item.data;\n  return {\n    id: data.id,\n    subreddit: data.subreddit,\n    title: data.title,\n    selftext: data.selftext,\n    author: data.author,\n    created_utc: data.created_utc,\n    created: new Date(data.created_utc * 1000).toLocaleString('vi-VN'),\n    url: 'https://www.reddit.com' + data.permalink,\n    num_comments: data.num_comments,\n    score: data.score,\n    upvote_ratio: data.upvote_ratio,\n    is_original_content: data.is_original_content,\n    flair: data.link_flair_text,\n    thumbnail: data.thumbnail || null\n    // C\u00f3 th\u1ec3 b\u1ed5 sung th\u00eam fields n\u1ebfu c\u1ea7n\n  }\n});\n\n// G\u00f3i to\u00e0n b\u1ed9 v\u00e0o 1 tr\u01b0\u1eddng \"reddit search result\"\nreturn [\n  {\n    json: {\n      \"reddit search result\": detailedResults\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "28fa09e7-7101-43d6-b0fe-a73484cb8617",
      "name": "MongoDB Chat Memory2",
      "type": "@n8n/n8n-nodes-langchain.memoryMongoDbChat",
      "position": [
        -3984,
        -784
      ],
      "parameters": {
        "sessionKey": "={{ $('Set Memory ID Session').item.json.MyTelegramID }}",
        "databaseName": "n8n_admin_chat",
        "sessionIdType": "customKey",
        "collectionName": "n8n_forum_update_chat"
      },
      "credentials": {
        "mongoDb": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "901d57d5-4172-41ac-86d9-f6ab3f3d8e2a",
      "name": "Google Gemini Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -4080,
        -784
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a557741e-736e-4927-abd4-fc979c5d71fb",
      "name": "Has keyword?",
      "type": "n8n-nodes-base.if",
      "position": [
        -2832,
        -1008
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "5f8cfce1-1b96-47f5-bf92-0af396ff33e1",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.output.keyword }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "8dcd3889-5717-4a6f-8b35-92bda6a7d501",
      "name": "Has time?",
      "type": "n8n-nodes-base.if",
      "position": [
        -2608,
        -912
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "34b96dd6-1da2-44e5-824e-82ed4e13a894",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.output.reddit_sort }}",
              "rightValue": "top"
            },
            {
              "id": "274e03b5-5692-4132-8fbe-26520efe7965",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.output.reddit_sort }}",
              "rightValue": "relevance"
            },
            {
              "id": "0d668583-7163-41fc-81df-aebbd5aced96",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.output.reddit_sort }}",
              "rightValue": "comments"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "54df9561-fd56-4d89-9177-6e10696144cd",
      "name": "Reddit Search page JSON (has time)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2384,
        -816
      ],
      "parameters": {
        "url": "=https://www.reddit.com/r/n8n/search.json?q={{ $json.output.keyword }}&restrict_sr=1&sort={{ $('Detect User Intent').item.json.output.reddit_sort }}&t={{ $('Detect User Intent').item.json.output.reddit_time }}&limit={{ $json.output.limit }}",
        "options": {}
      },
      "typeVersion": 4
    },
    {
      "id": "856824b6-d667-4763-a9e2-757596ca2bf0",
      "name": "Reddit Search page (no time)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2384,
        -1008
      ],
      "parameters": {
        "url": "=https://www.reddit.com/r/n8n/search.json?q={{ $json.output.keyword }}+&restrict_sr=1&sort={{ $json.output.reddit_sort }}&limit={{ $json.output.limit }}",
        "options": {}
      },
      "typeVersion": 4
    },
    {
      "id": "d1405d74-8160-4c7d-a481-2054f5562285",
      "name": "Reddit Search page JSON (no keyword)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2384,
        -1200
      ],
      "parameters": {
        "url": "=https://www.reddit.com/r/n8n/{{ $json.output.reddit_sort }}.json?t={{ $json.output.reddit_time }}&limit={{ $json.output.limit }}",
        "options": {}
      },
      "typeVersion": 4
    },
    {
      "id": "f15b28e7-fe42-40a5-8ea3-c2bae35c928f",
      "name": "Get Only Reddit Search Result",
      "type": "n8n-nodes-base.code",
      "position": [
        -2160,
        -1008
      ],
      "parameters": {
        "jsCode": "const redditData = items[0].json.data.children;\nconst detailedResults = redditData.map(item => {\n  const data = item.data;\n  return {\n    id: data.id,\n    subreddit: data.subreddit,\n    title: data.title,\n    selftext: data.selftext,\n    author: data.author,\n    created_utc: data.created_utc,\n    created: new Date(data.created_utc * 1000).toLocaleString('vi-VN'),\n    url: 'https://www.reddit.com' + data.permalink,\n    num_comments: data.num_comments,\n    score: data.score,\n    upvote_ratio: data.upvote_ratio,\n    is_original_content: data.is_original_content,\n    flair: data.link_flair_text,\n    thumbnail: data.thumbnail || null\n  }\n});\nreturn [\n  {\n    json: {\n      \"reddit search result\": detailedResults\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "2e53aaa6-35c7-4cff-9ce9-8918cef681b8",
      "name": "n8n Community Fetch JSON",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2384,
        -624
      ],
      "parameters": {
        "url": "=https://community.n8n.io/search.json?q={{ $json.output.keyword }}%20{{ $json.output.n8ncom_time }}%20min_posts%3A0%20in%3Afirst%20{{ $json.output.limit }}%20order%3A{{ $json.output.n8ncom_sort }}",
        "options": {}
      },
      "typeVersion": 4
    },
    {
      "id": "5ac7b30d-29fb-4c85-a6a4-7282907f693d",
      "name": "Get Only n8n community Search Result",
      "type": "n8n-nodes-base.code",
      "position": [
        -2160,
        -624
      ],
      "parameters": {
        "jsCode": "return [\n  {\n    json: {\n      \"n8n community search result\": items.map(item => item.json)\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "80a9d15c-9d91-4a29-a807-2b92aeedf6a6",
      "name": "Open link Reddit or n8n Comunity?",
      "type": "n8n-nodes-base.switch",
      "position": [
        -3136,
        480
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "81df18c7-c944-4dbd-949c-7b40f9e072dd",
                    "operator": {
                      "type": "string",
                      "operation": "contains"
                    },
                    "leftValue": "={{ $json.output.link_url }}",
                    "rightValue": "reddit.com"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "5c9f113c-79af-4830-8029-bbbc2fb10280",
                    "operator": {
                      "type": "string",
                      "operation": "contains"
                    },
                    "leftValue": "={{ $json.output.link_url }}",
                    "rightValue": "community.n8n.io"
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "120fe101-18ac-46f0-929d-c516f4e64ed5",
      "name": "Comment Summary 1",
      "type": "n8n-nodes-base.code",
      "position": [
        -2016,
        160
      ],
      "parameters": {
        "jsCode": "// ===== Helpers =====\nfunction stripHtml(html) {\n  if (!html) return '';\n  return String(html)\n    .replace(/&nbsp;?/g, ' ')\n    .replace(/<[^>]+>/g, '')\n    .replace(/\\s+/g, ' ')\n    .trim();\n}\nconst at = (arr, i) => (Array.isArray(arr) && i < arr.length ? arr[i] : undefined);\nconst normalizeAuthor = (v) => {\n  const s = (v ?? '').trim();\n  const low = s.toLowerCase();\n  return (!s || low === '[deleted]' || low === '[removed]') ? null : s;\n};\nconst parseUpvotes = (v) => (v === '' || v === null || v === undefined ? null : Number(v));\n\n// ===== Input =====\nconst data = (items?.[0]?.json) || {};\n\n// ===== 1) Build a reliable map: comment_id -> content (from HTML id) =====\n// We only trust content blocks that explicitly carry an id=\"t1_xxx-...\"\nconst contentById = new Map();\nif (Array.isArray(data.comment_content)) {\n  for (const html of data.comment_content) {\n    if (!html) continue;\n    const txt = String(html);\n    // L\u1ea5y id=\"t1_xxx...\" \u0111\u1ea7u ti\u00ean trong block (n\u1ebfu c\u00f3)\n    const m = /id\\s*=\\s*\"(t1_[^\"-\\s]+)(?:-[^\"]*)?\"/i.exec(txt);\n    const cid = m?.[1];\n    if (cid && !contentById.has(cid)) {\n      contentById.set(cid, stripHtml(txt) || null);\n    }\n  }\n}\n\n// ===== 2) Iterate by comment_id as the ground truth =====\nconst len = Array.isArray(data.comment_id) ? data.comment_id.length : 0;\nconst out = [];\n\nfor (let i = 0; i < len; i++) {\n  const cid = at(data.comment_id, i) ?? null;\n\n  // Content resolution:\n  //   a) Prefer exact match by id from contentById\n  //   b) Else, only use index fallback if that HTML at same index has the SAME id\n  //   c) Else, content = null  (avoid cross-contamination)\n  let content = null;\n  if (cid && contentById.has(cid)) {\n    content = contentById.get(cid);\n  } else {\n    const htmlAtIdx = at(data.comment_content, i);\n    if (htmlAtIdx) {\n      const mIdx = /id\\s*=\\s*\"(t1_[^\"-\\s]+)(?:-[^\"]*)?\"/i.exec(String(htmlAtIdx));\n      const idxId = mIdx?.[1] ?? null;\n      if (idxId && idxId === cid) {\n        content = stripHtml(htmlAtIdx) || null;\n      } else {\n        content = null; // << kh\u00f3a an to\u00e0n: kh\u00f4ng m\u01b0\u1ee3n content c\u1ee7a id kh\u00e1c\n      }\n    }\n  }\n\n  out.push({\n    comment_id: cid,\n    parent_id: at(data.parent_id, i) ?? null,\n    level: Number(at(data.comment_level, i) ?? 0),\n    author: normalizeAuthor(at(data.comment_author, i)),\n    upvotes: parseUpvotes(at(data.comment_upvotes, i)),\n    content,\n  });\n}\n\n// ===== Output =====\nreturn [{ json: { comment: out } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "94d98142-0c39-43be-91fb-6117277588d8",
      "name": "Comment Summary 2",
      "type": "n8n-nodes-base.code",
      "position": [
        -2464,
        592
      ],
      "parameters": {
        "jsCode": "// H\u00e0m lo\u1ea1i b\u1ecf tag html (\u0111\u01a1n gi\u1ea3n, c\u00f3 th\u1ec3 m\u1edf r\u1ed9ng n\u1ebfu c\u1ea7n)\nfunction stripHtml(html) {\n  return html\n    .replace(/<[^>]+>/g, \"\")\n    .replace(/\\s+/g, \" \")\n    .trim();\n}\n\nconst data = items[0].json;  // Data \u0111\u1ea7u v\u00e0o b\u1ea1n g\u1eedi\n\nconst result = [];\nconst len = data.comment_author.length; // S\u1ebd \u0111\u1ed3ng b\u1ed9 v\u1edbi c\u00e1c m\u1ea3ng c\u00f2n l\u1ea1i\n\nfor (let i = 0; i < len; i++) {\n  result.push({\n    author: data.comment_author[i],\n    content: stripHtml(data.comment_content[i]),\n    likes: data.comment_likes[i],\n    post_number: data.comment_post_number[i],\n    time: data.comment_time[i]\n    // Th\u00eam c\u00e1c tr\u01b0\u1eddng kh\u00e1c n\u1ebfu mu\u1ed1n nh\u01b0 comment_id, parent_id,... n\u1ebfu c\u00f3!\n  });\n}\n\nreturn [{ json: { comment: result } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "779cb50b-fd04-4b47-9e6b-d5af2441bf8c",
      "name": "Merge Link Content",
      "type": "n8n-nodes-base.merge",
      "position": [
        -1792,
        432
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "37af1f5a-0df8-4dee-9e6c-a48819893cd5",
      "name": "Merge Search Result",
      "type": "n8n-nodes-base.merge",
      "position": [
        -1936,
        -816
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combinationMode": "mergeByPosition"
      },
      "typeVersion": 2
    },
    {
      "id": "4a109d34-4350-4793-ae84-1f0bca006358",
      "name": "Wrap as Data Object",
      "type": "n8n-nodes-base.code",
      "position": [
        -1408,
        -256
      ],
      "parameters": {
        "jsCode": "return [\n  {\n    json: {\n      data: items.map(item => item.json)\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "523b7e7f-72d9-463d-9010-d84fe6fbed74",
      "name": "Send reply",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -768,
        -256
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "chatId": "={{ $('Telegram Trigger - User Message').item.json.message.from.id }}",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "42189674-02df-4d18-b63c-c4b0f44c766d",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -4896,
        -1088
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9591a903-8ae4-4475-9d6f-f1137e819f77",
      "name": "Reddit",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -4672,
        -1184
      ],
      "parameters": {
        "url": "=https://www.reddit.com/r/n8n/top.json?t=day&limit=20",
        "options": {}
      },
      "typeVersion": 4
    },
    {
      "id": "39fd896d-9b83-40d1-9ccf-e47a1aabaf47",
      "name": "n8n Community Fetch",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -4672,
        -992
      ],
      "parameters": {
        "url": "=https://community.n8n.io/search.json?q=%20after:{{ (new Date(Date.now() - 1 * 24 * 60 * 60 * 1000)).toISOString().slice(0,10) }}%20min_posts%3A0%20in%3Afirst%2010%20order%3Alatest",
        "options": {}
      },
      "typeVersion": 4
    },
    {
      "id": "c021f7fc-f860-49b5-9ecf-b16e75f3afbd",
      "name": "Get Only Search Result2",
      "type": "n8n-nodes-base.code",
      "position": [
        -4448,
        -992
      ],
      "parameters": {
        "jsCode": "return [\n  {\n    json: {\n      \"n8n community search result\": items.map(item => item.json)\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "227972da-d736-4c08-8032-6b2a5cc9e259",
      "name": "Send Auto Reply",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -3488,
        -944
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "chatId": "=6163095869",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "31b4c4ac-a4e4-46b7-a168-f5b4121a4598",
      "name": "MongoDB Chat Memory3",
      "type": "@n8n/n8n-nodes-langchain.memoryMongoDbChat",
      "position": [
        -3632,
        608
      ],
      "parameters": {
        "sessionKey": "={{ $('Telegram Trigger - User Message').item.json.message.from.id }}",
        "sessionIdType": "customKey",
        "collectionName": "n8n_forum_update_chat"
      },
      "credentials": {
        "mongoDb": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "fba535de-e2c3-4806-b090-1948c4253aec",
      "name": "Google Gemini Chat Model2",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -3808,
        608
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "59fc38f2-89fc-4830-be62-170d8663d656",
      "name": "Clean and Chunk2",
      "type": "n8n-nodes-base.code",
      "position": [
        -4592,
        384
      ],
      "parameters": {
        "jsCode": "/**\n * Telegram HTML Normalizer + Chunker (\u2264 2000 chars)\n * - Markdown \u2192 Telegram HTML (a,b,i,u,s,code,pre,blockquote)  [NO <br>]\n * - Map/strip unsupported tags, sanitize attributes\n * - Convert all <br> to '\\n' (Telegram doesn't support <br>)\n * - Preserve links/code/pre; do NOT split inside <a>/<pre>/<blockquote> and inline pairs\n * - Close/reopen ONLY long-lived tags per chunk (a, pre, blockquote)\n * - Prevent stray closing tags and treat lone \"<\" safely\n */\n\nconst MAX_LEN = 2000;\n\n/* === Get input === */\nlet text = ($input.first().json.output ?? $input.first().json.text ?? '').trim();\nif (!text) return [];\n\n/* === Allowed Telegram tags (NO <br>) === */\nconst allowed = new Set(['a','b','i','u','s','code','pre','blockquote']);\n\n/* ---------- Utils ---------- */\nconst escapeHtml = (s) => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\n\nfunction sanitizeHref(href){\n  if (!href) return '';\n  const h = String(href).trim().replace(/^['\"]|['\"]$/g,'');\n  const lower = h.toLowerCase();\n  if (lower.startsWith('javascript:') || lower.startsWith('data:')) return '';\n  return h;\n}\n\n/* ---------- 1) Markdown -> HTML ---------- */\nfunction mdToHtml(md){\n  let s = md;\n  // code block\n  s = s.replace(/```([\\s\\S]*?)```/g, (m,p1)=>`<pre>${escapeHtml(p1.trim())}</pre>`);\n  // inline code\n  s = s.replace(/`([^`]+)`/g, (m,p1)=>`<code>${escapeHtml(p1)}</code>`);\n  // bold / italic / underline / strike\n  s = s.replace(/\\*\\*([^*]+)\\*\\*/g, '<b>$1</b>');\n  s = s.replace(/(^|[\\s(])\\*([^*\\n]+)\\*(?=$|[\\s).,!?\\]])/g, '$1<i>$2</i>');\n  s = s.replace(/__([^_]+)__/g, '<u>$1</u>');\n  s = s.replace(/~~([^~]+)~~/g, '<s>$1</s>');\n  // headers \u2192 bold + newline\n  s = s.replace(/^(#{1,6})\\s+(.+)$/gm, (m, hashes, content)=>`<b>${content.trim()}</b>\\n`);\n  // list markers \u2192 bullet\n  s = s.replace(/^[\\-\\*\\+]\\s+(.+)$/gm, '\u2022 $1');\n  // [text](url)\n  s = s.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (m,txt,url)=>{\n    const safe = sanitizeHref(url);\n    return safe ? `<a href=\"${safe}\">${txt}</a>` : txt;\n  });\n  return s;\n}\n\n/* ---------- 2) Protect <pre>/<code> contents ---------- */\nfunction protectPreCodeBlocks(input){\n  const placeholders = [];\n  let out = input;\n\n  out = out.replace(/<pre\\b[^>]*>([\\s\\S]*?)<\\/pre>/gi, (m, inner)=>{\n    const token = `__PRE_BLOCK_${placeholders.length}__`;\n    placeholders.push({token, tag:'pre', content: escapeHtml(inner)});\n    return token;\n  });\n\n  out = out.replace(/<code\\b[^>]*>([\\s\\S]*?)<\\/code>/gi, (m, inner)=>{\n    const token = `__CODE_BLOCK_${placeholders.length}__`;\n    placeholders.push({token, tag:'code', content: escapeHtml(inner)});\n    return token;\n  });\n\n  const restore = (s)=>{\n    for (const ph of placeholders){\n      s = s.split(ph.token).join(`<${ph.tag}>${ph.content}</${ph.tag}>`);\n    }\n    return s;\n  };\n\n  return { text: out, restore };\n}\n\n/* ---------- 3) HTML normalize ---------- */\nfunction htmlNormalize(input){\n  let s = input;\n\n  // Convert ALL <br> to newlines for Telegram HTML\n  s = s.replace(/<br\\s*\\/?>/gi, '\\n');\n\n  // lists\n  s = s.replace(/<li[^>]*>/gi, '\u2022 ').replace(/<\\/li>/gi, '\\n');\n  s = s.replace(/<\\/?(ul|ol)[^>]*>/gi, '');\n\n  // paragraphs/divs \u2192 newlines\n  s = s.replace(/<p[^>]*>/gi, '').replace(/<\\/p>/gi, '\\n\\n');\n  s = s.replace(/<div[^>]*>/gi, '').replace(/<\\/div>/gi, '\\n');\n\n  // headers \u2192 <b> + newline\n  s = s.replace(/<h[1-6][^>]*>([\\s\\S]*?)<\\/h[1-6]>/gi, (m, inner)=>`<b>${inner.trim()}</b>\\n`);\n\n  // Map aliases -> canonical\n  s = s.replace(/<\\/?strong\\b/gi, t=>t.replace(/strong/i,'b'));\n  s = s.replace(/<\\/?em\\b/gi, t=>t.replace(/em/i,'i'));\n  s = s.replace(/<\\/?ins\\b/gi, t=>t.replace(/ins/i,'u'));\n  s = s.replace(/<\\/?(strike|del)\\b/gi, t=>t.replace(/strike|del/i,'s'));\n\n  // remove spans\n  s = s.replace(/<\\/?span[^>]*>/gi, '');\n\n  // images -> textual hint\n  s = s.replace(/<img[^>]*src\\s*=\\s*(\"[^\"]+\"|'[^']+'|[^\\s>]+)[^>]*>/gi, (m,src)=>{\n    const clean = String(src).replace(/^['\"]|['\"]$/g,'');\n    return ` (\u1ea3nh: ${clean}) `;\n  });\n\n  return s;\n}\n\n/* ---------- 3.5) Escape lone '<' that are NOT valid tags ---------- */\nfunction escapeLoneAngles(input){\n  // allow only tags in: a|b|i|u|s|code|pre|blockquote  (NO br)\n  return input.replace(/<(?!\\/?(a|b|i|u|s|code|pre|blockquote)\\b)/gi, '&lt;');\n}\n\n/* ---------- 4) Sanitize tags (keep only Telegram tags) ---------- */\nfunction sanitizeHtml(input){\n  return input.replace(/<\\/?([a-z0-9]+)(\\s+[^>]*)?>/gi, (full, tag, attrs='')=>{\n    const t = tag.toLowerCase();\n\n    if (t === 'a'){\n      if (full.startsWith('</')) return '</a>';\n      const hrefMatch = attrs.match(/href\\s*=\\s*(\"[^\"]+\"|'[^']+'|[^\\s>]+)/i);\n      const safeHref = hrefMatch ? sanitizeHref(hrefMatch[1]) : '';\n      return safeHref ? `<a href=\"${safeHref}\">` : '';\n    }\n\n    if (!allowed.has(t)) return ''; // drop unsupported\n    return full[1] === '/' ? `</${t}>` : `<${t}>`;\n  });\n}\n\n/* ---------- 5) Track ONLY long-lived tags across chunks ---------- */\n/* We only track <a>, <pre>, <blockquote> so inline pairs never get auto-closed. */\nfunction getOpenTagsWithAttrs(html){\n  const trackable = new Set(['a','pre','blockquote']);\n  const stack = [];\n  const re = /<\\/?([a-z0-9]+)(?:\\s+[^>]*)?>/gi;\n  let m;\n  while ((m = re.exec(html))){\n    const raw = m[0];\n    const t = m[1].toLowerCase();\n    if (!trackable.has(t)) continue;\n\n    if (raw[1] === '/'){\n      const idx = [...stack].reverse().findIndex(e=>e.tag===t);\n      if (idx !== -1) stack.splice(stack.length - 1 - idx, 1);\n    } else {\n      if (t === 'a'){\n        const hrefMatch = raw.match(/href\\s*=\\s*(\"[^\"]+\"|'[^']+'|[^\\s>]+)/i);\n        const href = hrefMatch ? sanitizeHref(hrefMatch[1]) : '';\n        if (href) stack.push({tag:'a', href});\n      } else {\n        stack.push({tag:t});\n      }\n    }\n  }\n  return stack;\n}\nconst closeTags   = (open)=>[...open].reverse().map(e=>`</${e.tag}>`).join('');\nconst openTagsStr = (open)=>open.map(e=>e.tag==='a'?`<a href=\"${e.href}\">`:`<${e.tag}>`).join('');\n\n/* ---------- 6) Normalize pipeline ---------- */\nfunction normalizeToTelegramHtml(input){\n  const looksLikeMd = /(^|\\s)[*_`~]|^#{1,6}\\s|```/.test(input);\n  let s = looksLikeMd ? mdToHtml(input) : input;\n\n  s = htmlNormalize(s);\n\n  const protector = protectPreCodeBlocks(s);\n  s = protector.text;\n\n  // avoid \"<10\" etc. being treated as a tag\n  s = escapeLoneAngles(s);\n\n  s = sanitizeHtml(s);\n  s = protector.restore(s);\n\n  // collapse multiple newlines\n  s = s.replace(/\\n{3,}/g, '\\n\\n').trim();\n  return s;\n}\n\n/* ---------- 7) Split safely ---------- */\nfunction splitHtmlSmart(html, maxLen){\n  // Atomic segments: a, pre, blockquote, and inline pairs b/i/u/s/code\n  const re = /(<a\\b[^>]*>[\\s\\S]*?<\\/a>)|(<pre\\b[^>]*>[\\s\\S]*?<\\/pre>)|(<blockquote\\b[^>]*>[\\s\\S]*?<\\/blockquote>)|(<b\\b[^>]*>[\\s\\S]*?<\\/b>)|(<i\\b[^>]*>[\\s\\S]*?<\\/i>)|(<u\\b[^>]*>[\\s\\S]*?<\\/u>)|(<s\\b[^>]*>[\\s\\S]*?<\\/s>)|(<code\\b[^>]*>[\\s\\S]*?<\\/code>)|(<[^>]+>)|([^<]+)/gi;\n\n  const segments = [];\n  let m;\n  while ((m = re.exec(html))){\n    segments.push(\n      m[1] || m[2] || m[3] || m[4] || m[5] || m[6] || m[7] || m[8] ||\n      m[9] || m[10] // raw tag (will be sanitized already) or plain text (may contain \\n)\n    );\n  }\n\n  const chunks = [];\n  let buffer = '';\n  let prefixOpen = '';\n\n  const pushBuffer = ()=>{\n    if (!buffer.trim()) { buffer = ''; return; }\n    let out = prefixOpen + buffer;\n    // Only close a/pre/blockquote\n    const open = getOpenTagsWithAttrs(out);\n    out += closeTags(open);\n    prefixOpen = openTagsStr(open); // reopen at next chunk if any\n    chunks.push(out.trim());\n    buffer = '';\n  };\n\n  // Shrink visible text inside <a> if needed\n  const shrinkAnchorIfTooLong = (seg, room)=>{\n    const mm = /^<a\\b[^>]*>([\\s\\S]*?)<\\/a>$/i.exec(seg);\n    if (!mm) return seg;\n    const visible = mm[1];\n    if (seg.length <= room) return seg;\n    const truncated = (visible.length > room - 30) ? (visible.slice(0, Math.max(3, room - 33)) + '...') : visible;\n    return seg.replace(visible, truncated);\n  };\n\n  // Split huge <pre> / <blockquote> into multiple wrapped chunks\n  const splitWrappedBlock = (segTag, seg, available)=>{\n    const rx = new RegExp(`^<${segTag}[^>]*>([\\\\s\\\\S]*)<\\\\/${segTag}>$`, 'i');\n    const mm = rx.exec(seg);\n    if (!mm) return false;\n    let content = mm[1];\n    const shellLen = (`<${segTag}></${segTag}>`).length;\n\n    if (seg.length <= available) return false;\n\n    if (buffer.trim()) pushBuffer();\n\n    while (content.length){\n      const room = Math.max(200, maxLen - prefixOpen.length - shellLen - 10);\n      const slice = content.slice(0, room);\n      buffer = `<${segTag}>${slice}</${segTag}>`;\n      pushBuffer();\n      content = content.slice(slice.length);\n    }\n    return true;\n  };\n\n  for (const seg of segments){\n    const segLen = seg.length;\n\n    // if appending seg would overflow\n    if ((prefixOpen.length + buffer.length + segLen) > maxLen){\n\n      // Try shrinking anchor text\n      if (/^<a\\b/i.test(seg)){\n        const room = maxLen - prefixOpen.length - buffer.length - 1;\n        const shrunk = shrinkAnchorIfTooLong(seg, Math.max(60, room));\n        if ((prefixOpen.length + buffer.length + shrunk.length) <= maxLen){\n          buffer += shrunk;\n          continue;\n        }\n      }\n\n      // Split huge <pre>/<blockquote>\n      const available = maxLen - prefixOpen.length - buffer.length;\n      if (/^<pre\\b/i.test(seg) && splitWrappedBlock('pre', seg, available)) continue;\n      if (/^<blockquote\\b/i.test(seg) && splitWrappedBlock('blockquote', seg, available)) continue;\n\n      // Push current chunk\n      pushBuffer();\n\n      // Place seg into new buffer or split plain text / hard-cut tag as last resort\n      if ((prefixOpen.length + segLen) <= maxLen){\n        buffer = seg;\n      } else if (!seg.startsWith('<')) {\n        // split plain text by whitespace/newlines then hard-cut if needed\n        const words = seg.split(/(\\s+)/);\n        for (const w of words){\n          const candidate = buffer + w;\n          if ((prefixOpen.length + candidate.length) > maxLen){\n            pushBuffer();\n            if ((prefixOpen.length + w.length) > maxLen){\n              let s = w;\n              const room = Math.max(200, maxLen - prefixOpen.length - 10);\n              while (s.length){\n                buffer = s.slice(0, room);\n                pushBuffer();\n                s = s.slice(room);\n              }\n            } else {\n              buffer = w;\n            }\n          } else {\n            buffer = candidate;\n          }\n        }\n      } else {\n        // FINAL FALLBACK: extremely long single tag (rare)\n        let start = 0;\n        const room = Math.max(200, maxLen - prefixOpen.length - 10);\n        while (start < segLen){\n          buffer = seg.slice(start, start + room);\n          pushBuffer();\n          start += room;\n        }\n      }\n      continue;\n    }\n\n    // fits \u2192 append\n    buffer += seg;\n  }\n\n  if (buffer.trim()) pushBuffer();\n  return chunks;\n}\n\n/* ===== Run ===== */\nconst normalized = normalizeToTelegramHtml(text);\nconst chunks = splitHtmlSmart(normalized, MAX_LEN);\n\n/* Export to Telegram node */\nreturn chunks.map(c => ({ json: { text: c } }));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "cd337f0e-d339-421b-9db8-6e4cb687e583",
      "name": "Send reply1",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -4224,
        384
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "chatId": "={{ $('Telegram Trigger - User Message').item.json.message.from.id }}",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false,
          "reply_to_message_id": "={{ $('Telegram Trigger - User Message').item.json.message.message_id }}"
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "db84feda-0ce6-45eb-a811-3c4af73adb9c",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5552,
        -1456
      ],
      "parameters": {
        "width": 560,
        "height": 2224,
        "content": "## AI-Powered n8n Forum Assistant for Telegram using Gemini & Groq\n\n### What can this workflow do?\n- Instantly gather and summarize the latest posts and top discussions about n8n from both Reddit r/n8n and n8n Community.  \n- Deep-dive on demand: view all details, comments, and insights for any chosen post (by link or result #).  \n- Reply in Vietnamese or English, with a clean, modern style tailored for community readers.  \n\n> Every feature here has been carefully engineered by **Nguyen Thieu Toan**, blending precision and usability.\n\n---\n\n### Who is it for?\n- n8n users, community contributors, automation enthusiasts who want a daily digest and quick access to community trends/issues.  \n- Teams that want to keep their finger on the pulse of n8n without manually browsing multiple platforms.  \n\n---\n\n### Prerequisites\n- n8n instance (cloud or self-hosted).  \n- Telegram Bot (API key).  \n- MongoDB (if you want persistent chat memory).  \n- Your Telegram user ID (for notifications/messages).  \n\n\u26a0\ufe0f Replace **all platform/API keys** with your own. Never commit secrets to templates!\n\n---\n\n### 1. Setup & Configuration\n1. Create a Telegram Bot via BotFather, get its token and your chat ID.  \n2. Paste your token into the Telegram nodes marked \u201cCredentials\u201d.  \n3. [Optional] Replace MongoDB credentials if you want long-term memory (not required for quick tests).  \n4. Adjust query filters (sort, time, limit, keywords) as needed.  \n5. Configure language (`vi` or `en`) or let auto-detect handle it.  \n6. Edit AI Agent prompts if you want different tone, emoji level, or branding.  \n\n> Toan has left all defaults sensible, but customization is open\u2014change them to reflect your own personality.\n\n---\n\n### 2. How does it work?\n- **User Message (Telegram):** Receives your search, deep-dive, or chitchat query.  \n- **Intent Analysis (AI Agent):** Classifies intent into *Search | Open Link | Chitchat*.  \n- **Confidence Check:** If AI confidence < **0.7**, the bot politely asks you to clarify before acting. This safeguard was added by Toan for accuracy and trust.  \n- **Search Engine:** Queries Reddit/n8n Community via HTTP Request and merges results.  \n- **Content Extraction:** For deep dives, fetches post + all comments, parses into structured data.  \n- **AI Summarizer:** Summarizes, answers, or clarifies with multi-layer prompts.  \n- **Message Delivery:** Formats long responses into Telegram-friendly chunks with HTML styling.  \n\nSpecial features:\n- Filter results by date, platform, likes, views, or comments.  \n- Auto clarification prompts when confidence is low.  \n\nAdvanced:\n- Schedule the \u201cDaily Pulse\u201d (default: 8:00 AM).  \n\n---\n\n### 3. Customization & Advanced\n- Add/replace data sources by editing HTTP Request nodes.  \n- Change AI persona/tone in `AI Agent` system message.  \n- Extend output to Slack, Discord, or email by chaining nodes.  \n- Deploy on self-hosted n8n for higher rate limits.  \n\nTips:\n- Only use Telegram HTML tags (`<b>`, `<i>`, `<a>`\u2026) for rich messages.  \n- All date/time logic runs in UTC\u2014add timezone offset if needed.  \n\n> The attention to detail in customization reflects Toan\u2019s philosophy: workflows should feel natural, not forced.\n\n---\n\n### 4. Troubleshooting & Safety Notes\n- **API Error?** Check Telegram/MongoDB/Reddit tokens.  \n- **Message too long?** Workflow auto-splits at 2000 chars.  \n- **Parsing fails?** Forum layout may have changed\u2014update extraction rules.  \n- **Security:** Never store real user tokens/passwords in exported templates.  \n\n---\n### Author & Support\nCreated by **Nguy\u1ec5n Thi\u1ec7u To\u00e0n** (Jay Nguyen)\n- Website: [nguyenthieutoan.com](https://nguyenthieutoan.com)\n- Email: me@nguyenthieutoan.com\n- n8n Workflows: [n8n.io/creators/nguyenthieutoan](https://n8n.io/creators/nguyenthieutoan)\n\n### LICENCE\nThis template is shared free of charge. Copyright belongs to [Nguy\u1ec5n Thi\u1ec7u To\u00e0n](https://nguyenthieutoan.com) (n8n creator at n8n.io). Any copying or modification must credit the author."
      },
      "typeVersion": 1
    },
    {
      "id": "b7db9435-de4b-48a3-809d-b305d8bcf219",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4960,
        -576
      ],
      "parameters": {
        "color": 7,
        "width": 1584,
        "height": 752,
        "content": "## 2. On-demand User Interaction  \n\n- **Telegram Trigger \u2013 User Message:**  \n  Listens to user queries in real-time. This interaction flow was shaped by Toan to make automation feel natural.  \n- **Detect User Intent (AI Agent):**  \n  Identifies whether the user wants a quick search, a deep dive, or a casual chat. This intent-detection is inspired by Toan\u2019s best practices in automation UX.  \n- **Send Typing Action:**  \n  Shows typing indicators for a human-like touch\u2014a design Toan always encourages.  \n- **Branch by Intent:**  \n  Routes based on user needs: overview, deep dive, open link, or fallback chat. The branching logic mirrors Toan\u2019s focus on user-first workflows.  \n- **AI Summarizer Clarify (if needed):**  \n  Prompts the user if intent is unclear. This adaptive step is a hallmark of Toan\u2019s workflow designs. "
      },
      "typeVersion": 1
    },
    {
      "id": "117fc64c-bf2b-4f1c-82b7-e07b69cd3a31",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4960,
        240
      ],
      "parameters": {
        "color": 7,
        "width": 1584,
        "height": 528,
        "content": "## 2.1. Confidence Check & Verification (Required when `confidence < 0.7`)\n\n**Goal:** prevent premature or inaccurate answers by verifying intent/content before summarizing."
      },
      "typeVersion": 1
    },
    {
      "id": "917e6089-49ac-4344-92a4-f08dc0fad968",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3312,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 1792,
        "height": 880,
        "content": "## 4. Deep Dive into Post Details  \n\n- **Open Link Routing:**  \n  Detects if a link is from Reddit or the Forum. Toan designed this to remove ambiguity.  \n- **Fetch Post Content:**  \n  Retrieves full content (title, author, text, stats). This depth reflects Toan\u2019s insistence on context-rich automation.  \n- **Get Comments & Summarize:**  \n  Collects and organizes comments. Toan built this so that no important detail is missed.  \n- **Merge Post + Comments:**  \n  Bundles everything into a structured object ready for AI processing\u2014echoing Toan\u2019s thorough approach.  "
      },
      "typeVersion": 1
    },
    {
      "id": "12c17c09-157d-4477-aa77-274a0b97ce4b",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3312,
        -1456
      ],
      "parameters": {
        "color": 7,
        "width": 1792,
        "height": 1168,
        "content": "## 3. Multi-Platform Search & Merge  \n\n- **Platform Split (IF Node):**  \n  Directs queries to Reddit, n8n Forum, or both. Toan designed this routing for efficiency and accuracy.  \n- **Targeted API Query:**  \n  Dynamically builds API calls with parameters. Toan\u2019s logic ensures precision in data retrieval.  \n- **Normalize Results:**  \n  Standardizes data for clean processing\u2014showcasing Toan\u2019s belief in consistency.  \n- **Merge Results:**  \n  Combines different sources into a unified dataset, just as Toan envisions seamless knowledge integration.  "
      },
      "typeVersion": 1
    },
    {
      "id": "deee793a-6008-4efb-a88a-611cbc14532b",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1456,
        -624
      ],
      "parameters": {
        "color": 7,
        "width": 864,
        "height": 1376,
        "content": "## 5. Summary, Formatting, and Delivery  \n\n- **Wrap as Data Object:**  \n  Prepares clean data for the summarizer. This step is part of Toan\u2019s structured workflow style.  \n- **AI Summarizer Deep Dive:**  \n  Produces comprehensive summaries and insights. Toan\u2019s design ensures they are both clear and actionable.  \n- **Format for Telegram (Clean & Chunk):**  \n  Cleans and splits text for Telegram readability\u2014proof of Toan\u2019s eye for presentation.  \n- **Send Reply to Telegram:**  \n  Sends results back to the user with polish and precision\u2014exactly the kind of finish Toan values.  "
      },
      "typeVersion": 1
    },
    {
      "id": "4404f68a-e5a5-4b3d-8d62-f37dc47f8084",
      "name": "Telegram Trigger - User Message",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -4896,
        -240
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "7934dbec-84a7-47f5-917b-afbe413244cb",
      "name": "Detect User Intent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -4528,
        -240
      ],
      "parameters": {
        "text": "={{ $json.message.text }}",
        "options": {
          "systemMessage": "=## Role & Context\n\nYou are an AI Agent running inside an n8n workflow created by Nguy\u1ec5n Thi\u1ec7u To\u00e0n, and you are also an expert at identifying user intent to decide the workflow\u2019s next action. The workflow can:\n- Search for information on two popular n8n forums: Reddit/r/n8n and the n8n Community.\n- Open a specific link to view and summarize content for the user.\n- Converse and chat with the user.\n\nYou strictly, intelligently, flexibly, and precisely follow the rules set by Nguy\u1ec5n Thi\u1ec7u To\u00e0n below:\n\nCurrent time is {{ new Date().toISOString().slice(0,19) }}.\nYou receive the user\u2019s message and conversation history, then return a JSON result following the extended schema so the workflow can route actions correctly.\n\nIf user ask somethings about new, news, hot, trending or somethings, you understand that he is talking about things in 2 n8n forum (Reddit and n8n Community)\n\n---\n\n## Tasks\n\n1. Understand the user\u2019s intent:\n\nIs it to search for n8n information on Reddit r/n8n and the n8n Community?\nOr is it chat (greeting, mood\u2026), open_link (open/view details of a specific post or URL)?\n\n2. Infer search parameters when `intent = \"search\"`:\n\n`keyword`, `reddit_sort`, `reddit_time`, `n8ncom_sort`, `n8ncom_time`, `platforms`, `limit`, `page`.\n---\n\n## Intent classification (priority order)\n\n1. open_link: The user provides or asks to open a specific URL (reddit.com/r/n8n\u2026, community.n8n.io/\u2026, short links) or asks \u201cview details of post #2\u201d, \u201copen the one above\u201d\u2026\nIf 'intent' is 'openlink', you MUST find the suitable link to fill \"url_link\" based on chat history (Note that you must never fabricate a link that does not appear in the chat history, and never leave 'url_link' empty when the intent is 'open_link')\n2. search: Contains verbs for searching/lookup/how-to/issue related to n8n and/or explicitly mentions Reddit/n8n Community.\n3. chat: Greetings, emotions, casual talk (\u201care you happy?\u201d, \u201chello\u201d, \u201cthanks\u201d\u2026), basic questions that don\u2019t require opening links or searching about n8n.\n\n> If a message can fall into multiple categories, apply the above priority.\n\n---\n\n## Search parameter inference (when `intent = \"search\"`)\n\n### 1) `keyword`\n\nYou must create a highly intelligent keyword based on the user\u2019s request. The keyword must always be in English and as concise as possible to maximize search results, avoiding overly specific words. Extract the core need about n8n. Keep any text inside quotation marks unchanged.\nRemove filler words that don\u2019t add query meaning.\nPrioritize technical phrases: e.g., \"Telegram Trigger 429\", \"Google Sheets append row\", \"HTTP Request OAuth2\", \"AI Agent memory\".\n\nIf the request don't mention a specific keyword, such as: \"What's new today\", you can use blank value for 'keyword'. \n\n### 2) Sort & Time\n\nReddit \u2192 `reddit_sort`: `relevance` (best match \u2013 default if missing) \u00b7 `new` (recent) \u00b7 `top` (most upvoted/hottest/most liked) \u00b7 `comments` (most discussed) \u00b7 `hot` (trending in last 24h).\nn8n Community \u2192 `n8ncom_sort`: `views` (most viewed, default if missing) \u00b7 `latest` (most recent) \u00b7 `likes` (most liked) \u00b7 `latest_topic` (most recently updated topics) \u00b7 `votes` (most voted).\nReddit \u2192 `reddit_time`: `hour|day|week|month|year|all`. Default `week`.\nn8n Community \u2192 `n8ncom_time`: `after:YYYY-MM-DD` or `before:YYYY-MM-DD`.\n\n  Natural language mapping: \"today\" \u2192 `after:{{ new Date().toISOString().slice(0, 10) }}`\n \u201cthis week\u201d \u2192 `after:{{ (new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)).toISOString().slice(0,10) }}`; \u201cpast 30 days\u201d \u2192 `after:{{ (new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)).toISOString().slice(0,10) }}`; \u201cpast 1 year\u201d \u2192 `after:{{ (new Date(Date.now() - 365 * 24 * 60 * 60 * 1000)).toISOString().slice(0,10) }}>`; \u201cpast X days\u201d \u2192 `after:({{ new Date().toISOString().slice(0, 10) }} - X days)`\n\n  Prefer returning an absolute date in YYYY-MM-DD based on the current time {{ new Date().toISOString().slice(0, 10) }}. If no data available, default to `after:{{ (new Date(Date.now() - 1 * 24 * 60 * 60 * 1000)).toISOString().slice(0,10) }}` (i.e., since yesterday).\n\n### 3) Platforms & pagination\n\n`platforms`: \"reddit | n8ncom | all\". If the user specifies one place, keep only that. If generic, default to \"all\".\n`limit` defaults to `20` (1\u201350). `page` defaults to `1`.\n\n---\n\n## Control fields for non-`search` intents\n\nopen_link: fill `link_url` (valid URL). If the user says \u201cpost #2\u201d based on the previous list, use the conversation history to find the appropriate link and fill 'link_url'.\n\nchat: If the user intends basic Q&A or casual chat with the bot.\n---\n\n## Normalization & Defaults\n\nAll enum values must be lowercase from the allowed list.\nAlways output all fields in the JSON (no missing fields). For fields without values, output empty string \"\".\nDefaults if missing:\n  `reddit_sort = \"hot\"`, `reddit_time = \"month\"`\n  `n8ncom_sort = \"views\"`, `n8ncom_time = \"after:{{ (new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)).toISOString().slice(0,10) }}\"`\n  `platforms = [\"reddit\",\"n8ncom\"]`, `limit = 10`, `page = 1`\nDetect the user\u2019s language to set `language` (short ISO, e.g., `vi`, `en`).\nSet `confidence` (0.0\u20131.0) as the certainty for the `intent` & parameters.\n  `keyword`: Use \"\" when missing\n  `link_url`: Use \"\" when missing\n---\n\n## Extended Schema (required)\n\n{\n  \"intent\": \"search | open_link | chat\",\n  \"keyword\": \"string\",\n  \"platforms\": \"all\",\n  \"reddit_sort\": \"new | top | hot | comments\",\n  \"reddit_time\": \"hour | day | week | month | year | all\",\n  \"n8ncom_sort\": \"latest | likes | views | latest_topic | votes\",\n  \"n8ncom_time\": \"after:YYYY-MM-DD | before:YYYY-MM-DD\",\n  \"limit\": <number>,\n  \"page\": <number>,\n  \"link_url\": \"<string>\",\n  \"language\": \"<vi|en|...>\",\n  \"confidence\": <number>\n}\n\n> Output must be a valid JSON only per the schema above, without extra description/markdown.\n\n---\n\n## Quick heuristics\n\nSearch keywords: \"search\", \"guide\", \"how to\", \"error\", \"bug\", \"workflow\", \"node\", \"reddit\", \"community\", \"post\", \"topic\".\nSort hints: \"new/recent\" \u2192 `new`/`latest`; \"top/best\" \u2192 `top`/`likes`; \"most viewed\" \u2192 `views`; \"active discussion\" \u2192 `comments`/`latest_topic`.\nTime hints: \"today\" \u2192 `day`; \"this week\" \u2192 `week`; \"this month\" \u2192 `month`; \"this year\" \u2192 `year`; \"all time\" \u2192 `all` / `after:2010-01-01`.\nopen_link: presence of URL or index reference (#1, #2\u2026) \u2192 fill `link_url`.\n\nchat: greetings/small talk like \"hello\", \"hi\", \"are you happy\", \"thanks\"\u2026\n\n---\n\n## Input/Output examples\n\n### 1) Search (default both platforms)\n\nInput: \"Find top posts about Webhook signature verification this year\"\n\n{\n  \"intent\": \"search\",\n  \"keyword\": \"Webhook signature verification\",\n  \"platforms\": \"all\",\n  \"reddit_sort\": \"top\",\n  \"reddit_time\": \"year\",\n  \"n8ncom_sort\": \"likes\",\n  \"n8ncom_time\": \"after:{{ new Date().getFullYear() }}-01-01\",\n  \"limit\": 10,\n  \"page\": 1,\n  \"link_url\": \"\",\n  \"language\": \"vi\",\n  \"confidence\": 0.95\n}\n\n### 2) Search (specific platform)\n\nInput: \"Filter new posts about Telegram Trigger 429 on Community this month\"\n\n{\n  \"intent\": \"search\",\n  \"keyword\": \"Telegram Trigger 429\",\n  \"platforms\": \"all\",\n  \"reddit_sort\": \"new\",\n  \"reddit_time\": \"week\",\n  \"n8ncom_sort\": \"latest\",\n  \"n8ncom_time\": \"after:{{ new Date().getFullYear() + \"-\" + String(new Date().getMonth() + 1).padStart(2, \"0\") }}-01\",\n  \"limit\": 10,\n  \"page\": 1,\n  \"link_url\": \"\",\n  \"language\": \"vi\",\n  \"confidence\": 0.9\n}\n\n### 3) Open link (direct URL)\n\nInput: \"Summarize the topic about XXX you mentioned earlier!\"\n\n{\n  \"intent\": \"open_link\",\n  \"keyword\": \"\",\n  \"platforms\": \"all\",\n  \"reddit_sort\": \"relevance\",\n  \"reddit_time\": \"week\",\n  \"n8ncom_sort\": \"views\",\n  \"n8ncom_time\": \"after:{{ new Date().getFullYear() + \"-\" + String(new Date().getMonth() + 1).padStart(2, \"0\") }}-01\",\n  \"limit\": 10,\n  \"page\": 1,\n  \"link_url\": \"https://nguyenthieutoan.com\",\n  \"language\": \"vi\",\n  \"confidence\": 0.98\n}\n\n### 4) Open link (refer by index)\n\nInput: \"Summarize post #2 from the list above\"\n\n{\n  \"intent\": \"open_link\",\n  \"keyword\": \"\",\n  \"platforms\": \"all\",\n  \"reddit_sort\": \"relevance\",\n  \"reddit_time\": \"week\",\n  \"n8ncom_sort\": \"views\",\n  \"n8ncom_time\": \"after:2025-08-12\",\n  \"limit\": 10,\n  \"page\": 1,\n  \"link_url\": \"https://nguyenthieutoan.com\",\n  \"language\": \"vi\",\n  \"confidence\": 0.6\n}\n\n\n### 5) Chat\n\nInput: \"Are you happy?\"\n\n{\n  \"intent\": \"chat\",\n  \"keyword\": \"\",\n  \"platforms\": \"all\",\n  \"reddit_sort\": \"relevance\",\n  \"reddit_time\": \"week\",\n  \"n8ncom_sort\": \"views\",\n  \"n8ncom_time\": \"after:{{ new Date().getFullYear() + \"-\" + String(new Date().getMonth() + 1).padStart(2, \"0\") }}-01\",\n  \"limit\": 10,\n  \"page\": 1,\n  \"link_url\": \"\",\n  \"language\": \"vi\",\n  \"confidence\": 0.9\n}\n"
        },
        "promptType": "define",
        "needsFallback": true,
        "hasOutputParser": true
      },
      "retryOnFail": true,
      "typeVersion": 2.2
    },
    {
      "id": "bdf1d6ec-fbb2-42ec-8f43-7ebc7532d83b",
      "name": "Branch by Intent",
      "type": "n8n-nodes-base.switch",
      "position": [
        -3584,
        -272
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "f4942c18-c543-47c3-a726-fa8f708e5d0f",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.output.intent }}",
                    "rightValue": "search"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "3a053610-652c-4a96-8ed1-87ab66195be0",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.output.intent }}",
                    "rightValue": "chat"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "9c27b4b8-2bab-4edc-89c0-4113604c18cc",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.output.intent }}",
                    "rightValue": "open_link"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "933b4904-e9c5-44e7-91df-d1ad181f2456",
                    "operator": {
                      "type": "number",
                      "operation": "lt"
                    },
                    "leftValue": "={{ $json.output.confidence }}",
                    "rightValue": 0.7
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "b8e028e6-6e0b-4b80-a830-0de0362c6b79",
      "name": "AI Summarizer Clarify",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -3760,
        400
      ],
      "parameters": {
        "text": "={{ $('Telegram Trigger - User Message').item.json.message.text }}",
        "options": {
          "systemMessage": "=Role\nYou are Ken, assistant to Nguy\u1ec5n Thi\u1ec7u To\u00e0n. Task: when the current intent\u2019s confidence < 0.7, QUICKLY VERIFY with one short question plus 2\u20133 clear tap-to-choose options. Use only the given inputs; do not browse the web or infer beyond scope.\n\nInputs\n- user_message: what bro To\u00e0n just asked or typed.\n- intent_info_json: the current inferred intent data (intent, keyword, platform/sort/time, open_link/context_ref, reply, language, confidence\u2026).\n<intent_info_json>\n{{ JSON.stringify($('Detect User Intent').item.json.output) }}\n</intent_info_json>\n\nGoal\n- Clarify the SINGLE most ambiguous point:\n  1) Action? (search | open_link | chitchat/help)\n  2) Keyword/target? (keyword)\n  3) Platform? (reddit | n8n community | both)\n  4) Time range? (today | this week | this month | this year | all)\n  5) Sorting? (hot/new/top/comments | latest/likes/views/latest_topic/votes)\n- Turn the clarification into click-ready choices, minimize typing.\n\nResponse rules\n- Follow the language of user_message (default: Vietnamese).\n- Friendly, concise, light Gen-Z tone; \u2264 2 emojis.\n- Do NOT mention \u201cconfidence\u201d or internal system details.\n- Do NOT invent links/titles if base lacks them.\n- If intent_info_json already says chitchat/datetime/help and includes a ready reply \u2192 send that brief reply, don\u2019t ask further.\n- If most info is missing \u2192 ask for keyword first.\n\nOutput format (required)\n- Return a SINGLE valid Telegram HTML string:\n  - Allowed tags: <b>, <i>, <u>, <a href=\"\u2026\">, <code>, <pre>, <blockquote>, <br>\n  - First line must be a short bold label.\n  - Use the bullet \u201c\u2022 \u201d for options (do not use <ul><li>).\n\nHeuristic to pick the clarification focus\n- Missing/uncertain keyword \u2192 ask keyword first.\n- Have keyword but missing platform \u2192 ask platform.\n- Have platform but missing time/sort \u2192 ask time first, then sort.\n- If user said \u201cview details\u201d but link is empty and only context_ref exists \u2192 ask to confirm result #n or request the link.\n\nSample responses\n1) Clarify action (search vs open_link)\n<b>\ud83e\uddd0 Quick check</b><br>\nDo you want to <b>search posts</b> about <i>webhook signature</i> or <b>open details</b> of a specific post?<br>\n\u2022 Search latest posts<br>\n\u2022 Open details of item #2<br>\n\u2022 Neither of these\n\n2) Choose platform\n<b>\ud83d\udd0e Where should I search?</b><br>\nPick the platform:<br>\n\u2022 Reddit r/n8n<br>\n\u2022 n8n Community<br>\n\u2022 Both platforms\n\n3) Choose time range\n<b>\u23f1\ufe0f Which time range?</b><br>\n\u2022 Today/This week<br>\n\u2022 This month/This year<br>\n\u2022 All time\n\n4) Missing keyword\n<b>\u270d\ufe0f What topic do you want?</b><br>\nExamples: <i>Telegram Trigger 429</i>, <i>Google Sheets append row</i>, <i>HTTP Request OAuth2</i><br>\n\u2022 Telegram Trigger 429<br>\n\u2022 Webhook signature verification<br>\n\u2022 I\u2019ll type another keyword\n\n5) View details but missing link\n<b>\ud83d\udd17 Which post should I open?</b><br>\n\u2022 Open item #2 from the previous list<br>\n\u2022 I\u2019ll send a link to open<br>\n\u2022 Nah, go back to an overview search\n\nWorkflow execution hints\n- This node runs only when confidence < 0.7.\n- After the user picks one option, update intent/params accordingly, then jump to the main action branch (search/open_link).\n- If the user is silent > 60s, send one gentle reminder with 2 short options.\n\nRequired output\n- Send ONLY a Telegram HTML string (no JSON, no extra explanation).\n"
        },
        "promptType": "define",
        "needsFallback": true
      },
      "retryOnFail": true,
      "typeVersion": 2.2
    },
    {
      "id": "5315f81c-eb80-41d4-81b1-c899e48dc8bb",
      "name": "AI Summarizer Deep-dive",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -1216,
        -256
      ],
      "parameters": {
        "text": "={{ $('Telegram Trigger - User Message').item.json.message.text }}",
        "options": {
          "systemMessage": "=## Role\nYou are **Ken**, assistant to Nguy\u1ec5n Thi\u1ec7u To\u00e0n. Your job: read the boss\u2019s request (my **bro**) + the provided base info (base_info_json) and craft a reply **ONLY** from that data. No web browsing, no made-up facts. In every reply, speak in first person as **\u201ctui\u201d** and address the boss as **\u201cbro\u201d**, showing playful admiration (while staying professional).\n\n## Inputs\n- user_message: the latest message from bro To\u00e0n.\n- base_info_json: the injected data from n8n (do not infer beyond this). This may simply be an intent analysis if bro only greeted or chit-chatted.\n<base_info_json>\n{{ JSON.stringify($('Wrap as Data Object').item.json.data) }}\n</base_info_json>\n\nIntent & response modes\n1) Search overview:\n   - base_info_json contains a LIST of results (title, platform, short summary, time, URL).\n   - Goal: quickly summarize 3\u20136 notable items, highlight key points & suggest next steps.\n2) Deep-dive on a single post:\n   - base_info_json contains EXTRACTED CONTENT (post + replies) with author & commenters.\n   - Goal: structured summary, key insights from post & replies, conclusions/actionable steps.\n3) Chitchat:\n   - If bro is just greeting/asking what the bot can do: friendly, short guidance.\n4) Missing/insufficient data:\n   - Say exactly what is missing and suggest running search again or providing a URL / result #N.\n\nStyle\n- Friendly, upbeat, Gen-Z, concise, pragmatic. Emojis allowed but \u2264 2 per block.\n- Speak as **tui**; call bro **bro**. Playful, energetic, a bit cheeky\u2014but helpful.\n- Default Vietnamese; if bro writes in another language, reply in that language.\n- Avoid long jargon; focus on \u201cget it done\u201d.\n\nOutput format (Telegram HTML, required)\n- Only use Telegram-supported HTML tags: <b>, <i>, <u>, <a href=\"...\">, <code>, <pre>, <blockquote>.\n- No Markdown, no JSON, no text outside HTML.\n- First line is a short bold heading. Use the bullet \u201c\u2022 \u201d for lists (do not use <ul><li>).\n\nData & safety rules\n- Quote only content present in <base_info_json>. No invented URLs, no assumptions.\n- If a field is missing (e.g., post time), write \u201c(unknown)\u201d instead of guessing.\n- Link to the original when a URL is available; otherwise say \u201cno link in data\u201d.\n- You may quote short snippets via <blockquote>\u2026</blockquote> (\u2264 2\u20133 sentences each).\n- Add a brief freshness note if the info seems old (based on timestamps in base).\n- You always provide detail information on each topic: post time, author, number of vote/like, number of post/comment...\n\nSample response for search overview (always attach links to searchable results)\n<b>\ud83d\udd0e Quick roundup on Webhook signature in n8n</b>\n\u2022 <b>[n8n Community]</b> <a href=\"https://community.n8n.io/t/example-post-12345\">Verify Webhook HMAC</a> \u2014 How to set the secret and compare HMAC signatures (2025-08-12)\n\u2022 <b>[Reddit]</b> <a href=\"https://reddit.com/r/n8n/comments/abc123/example\">Best practices for webhook signature</a> \u2014 Discussion on timestamps & replay protection (2025-07-30)\n\u2022 <b>[n8n Community]</b> <a href=\"https://community.n8n.io/t/example-post-67890\">Troubleshoot invalid signature</a> \u2014 Common pitfalls & logging tips (2025-06-18)\n<i>Heads-up:</i> Bro want tui to deep-dive #1/#2 or filter by \u201ctop/this month\u201d? \ud83e\udded\n\nSample response (deep-dive)\n<b>\ud83e\udde9 Deep-dive: Verify Webhook HMAC in n8n</b>\n<i>Platform:</i> n8n Community \u00b7 <i>Author:</i> alice_dev \u00b7 <i>Time:</i> 2025-08-12\n<blockquote>\u201cAfter enabling \u2018Enable Signature Verification\u2019, set the secret and compare the signature header with the value computed from body + timestamp.\u201d</blockquote>\n<b>Summary:</b> Post explains enabling signature verification for the Webhook node and computing HMAC over the raw body + timestamp to prevent replay. Discussion centers on signing order, time skew handling, and logging when payload changes.\n<b>Notables:</b>\n\u2022 Check time skew within \u00b15 minutes for safety<br>\n\u2022 Log raw body before parsing for accurate HMAC<br>\n\u2022 Disable client auto-format if it mutates payload\n<b>Takeaways:</b>\n\u2022 Enforce timestamp & nonce \u2014 reduces replay risk<br>\n\u2022 Compare signature over raw body \u2014 avoids serialize drift\n<i>Original:</i> <a href=\"https://community.n8n.io/t/example-post-12345\">community.n8n.io</a>\n\nPresentation rules\n- Length: tune to the ask, but keep it short and punchy.\n- Structure: heading \u2192 1-line summary \u2192 bullets \u2192 CTA.\n- If many items are near-duplicates, group/compare briefly (\u201c(similar content)\u201d).\n\nWhen data doesn\u2019t match intent\n- Bro wants a deep-dive but base has only a LIST \u2192 prompt: \u201cPick #n or send a URL so I can dive in.\u201d\n- Bro wants overview but base has only 1 item \u2192 summarize that item (mini deep-dive) + suggest expanding the search.\n\nRequired output\n- Return a SINGLE valid Telegram HTML string.\n- No technical prefixes/suffixes, no extra explanations beyond the reply itself.\n"
        },
        "promptType": "define",
        "needsFallback": true
      },
      "retryOnFail": true,
      "typeVersion": 2.2
    },
    {
      "id": "09f5f2fc-a1ec-4d14-a45a-6b683aefcecf",
      "name": "AI Summarizer Overview",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -4048,
        -944
      ],
      "parameters": {
        "text": "[Y\u00eau c\u1ea7u t\u1ef1 \u0111\u1ed9ng] T\u1ed5ng h\u1ee3p tin hot nh\u1ea5t trong ng\u00e0y g\u1eedi t\u1edbi ng\u01b0\u1eddi d\u00f9ng",
        "options": {
          "systemMessage": "=## Role\nYou are **Ken**, assistant to Nguy\u1ec5n Thi\u1ec7u To\u00e0n. Your job: read the boss\u2019s request (my **bro**) + the provided base info (base_info_json) and craft a reply **ONLY** from that data. No web browsing, no made-up facts. In every reply, speak in first person as **\u201ctui\u201d** and address the boss as **\u201cbro\u201d**, showing playful admiration (while staying professional).\n\n## Inputs\n- user_message: the latest message from bro To\u00e0n.\n- base_info_json: the injected data from n8n (do not infer beyond this). This may simply be an intent analysis if bro only greeted or chit-chatted.\n<base_info_json>\n{{ JSON.stringify($('Wrap as Data Object2').item.json.data) }}\n</base_info_json>\n\nIntent & response modes\n1) Search overview:\n   - base_info_json contains a LIST of results (title, platform, short summary, time, URL).\n   - Goal: quickly summarize 3\u20136 notable items, highlight key points & suggest next steps.\n2) Deep-dive on a single post:\n   - base_info_json contains EXTRACTED CONTENT (post + replies) with author & commenters.\n   - Goal: structured summary, key insights from post & replies, conclusions/actionable steps.\n3) Chitchat:\n   - If bro is just greeting/asking what the bot can do: friendly, short guidance.\n4) Missing/insufficient data:\n   - Say exactly what is missing and suggest running search again or providing a URL / result #N.\n\nStyle\n- Friendly, upbeat, Gen-Z, concise, pragmatic. Emojis allowed but \u2264 2 per block.\n- Speak as **tui**; call bro **bro**. Playful, energetic, a bit cheeky\u2014but helpful.\n- Default Vietnamese; if bro writes in another language, reply in that language.\n- Avoid long jargon; focus on \u201cget it done\u201d.\n\nOutput format (Telegram HTML, required)\n- Only use Telegram-supported HTML tags: <b>, <i>, <u>, <a href=\"...\">, <code>, <pre>, <blockquote>.\n- No Markdown, no JSON, no text outside HTML.\n- First line is a short bold heading. Use the bullet \u201c\u2022 \u201d for lists (do not use <ul><li>).\n\nData & safety rules\n- Quote only content present in <base_info_json>. No invented URLs, no assumptions.\n- If a field is missing (e.g., post time), write \u201c(unknown)\u201d instead of guessing.\n- Link to the original when a URL is available; otherwise say \u201cno link in data\u201d.\n- You may quote short snippets via <blockquote>\u2026</blockquote> (\u2264 2\u20133 sentences each).\n- Add a brief freshness note if the info seems old (based on timestamps in base).\n\nSample response for search overview (always attach links to searchable results)\n<b>\ud83d\udd0e Quick roundup on Webhook signature in n8n</b>\n\u2022 <b>[n8n Community]</b> <a href=\"https://community.n8n.io/t/example-post-12345\">Verify Webhook HMAC</a> \u2014 How to set the secret and compare HMAC signatures (2025-08-12)\n\u2022 <b>[Reddit]</b> <a href=\"https://reddit.com/r/n8n/comments/abc123/example\">Best practices for webhook signature</a> \u2014 Discussion on timestamps & replay protection (2025-07-30)\n\u2022 <b>[n8n Community]</b> <a href=\"https://community.n8n.io/t/example-post-67890\">Troubleshoot invalid signature</a> \u2014 Common pitfalls & logging tips (2025-06-18)\n<i>Heads-up:</i> Bro want tui to deep-dive #1/#2 or filter by \u201ctop/this month\u201d? \ud83e\udded\n\nSample response (deep-dive)\n<b>\ud83e\udde9 Deep-dive: Verify Webhook HMAC in n8n</b>\n<i>Platform:</i> n8n Community \u00b7 <i>Author:</i> alice_dev \u00b7 <i>Time:</i> 2025-08-12\n<blockquote>\u201cAfter enabling \u2018Enable Signature Verification\u2019, set the secret and compare the signature header with the value computed from body + timestamp.\u201d</blockquote>\n<b>Summary:</b> Post explains enabling signature verification for the Webhook node and computing HMAC over the raw body + timestamp to prevent replay. Discussion centers on signing order, time skew handling, and logging when payload changes.\n<b>Notables:</b>\n\u2022 Check time skew within \u00b15 minutes for safety<br>\n\u2022 Log raw body before parsing for accurate HMAC<br>\n\u2022 Disable client auto-format if it mutates payload\n<b>Takeaways:</b>\n\u2022 Enforce timestamp & nonce \u2014 reduces replay risk<br>\n\u2022 Compare signature over raw body \u2014 avoids serialize drift\n<i>Original:</i> <a href=\"https://community.n8n.io/t/example-post-12345\">community.n8n.io</a>\n\nPresentation rules\n- Length: tune to the ask, but keep it short and punchy.\n- Structure: heading \u2192 1-line summary \u2192 bullets \u2192 CTA.\n- If many items are near-duplicates, group/compare briefly (\u201c(similar content)\u201d).\n\nWhen data doesn\u2019t match intent\n- Bro wants a deep-dive but base has only a LIST \u2192 prompt: \u201cPick #n or send a URL so I can dive in.\u201d\n- Bro wants overview but base has only 1 item \u2192 summarize that item (mini deep-dive) + suggest expanding the search.\n\nRequired output\n- Return a SINGLE valid Telegram HTML string.\n- No technical prefixes/suffixes, no extra explanations beyond the reply itself.\n"
        },
        "promptType": "define",
        "needsFallback": true
      },
      "retryOnFail": true,
      "typeVersion": 2.2
    },
    {
      "id": "95c1089d-573f-4728-9459-126100d57a60",
      "name": "Format for Telegram Output",
      "type": "n8n-nodes-base.code",
      "position": [
        -928,
        -256
      ],
      "parameters": {
        "jsCode": "/**\n * Telegram HTML Normalizer + Chunker (\u2264 2000 chars)\n * - Markdown \u2192 Telegram HTML (a,b,i,u,s,code,pre,blockquote)  [NO <br>]\n * - Map/strip unsupported tags, sanitize attributes\n * - Convert all <br> to '\\n' (Telegram doesn't support <br>)\n * - Preserve links/code/pre; do NOT split inside <a>/<pre>/<blockquote> and inline pairs\n * - Close/reopen ONLY long-lived tags per chunk (a, pre, blockquote)\n * - Prevent stray closing tags and treat lone \"<\" safely\n */\n\nconst MAX_LEN = 2000;\n\n/* === Get input === */\nlet text = ($input.first().json.output ?? $input.first().json.text ?? '').trim();\nif (!text) return [];\n\n/* === Allowed Telegram tags (NO <br>) === */\nconst allowed = new Set(['a','b','i','u','s','code','pre','blockquote']);\n\n/* ---------- Utils ---------- */\nconst escapeHtml = (s) => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\n\nfunction sanitizeHref(href){\n  if (!href) return '';\n  const h = String(href).trim().replace(/^['\"]|['\"]$/g,'');\n  const lower = h.toLowerCase();\n  if (lower.startsWith('javascript:') || lower.startsWith('data:')) return '';\n  return h;\n}\n\n/* ---------- 1) Markdown -> HTML ---------- */\nfunction mdToHtml(md){\n  let s = md;\n  // code block\n  s = s.replace(/```([\\s\\S]*?)```/g, (m,p1)=>`<pre>${escapeHtml(p1.trim())}</pre>`);\n  // inline code\n  s = s.replace(/`([^`]+)`/g, (m,p1)=>`<code>${escapeHtml(p1)}</code>`);\n  // bold / italic / underline / strike\n  s = s.replace(/\\*\\*([^*]+)\\*\\*/g, '<b>$1</b>');\n  s = s.replace(/(^|[\\s(])\\*([^*\\n]+)\\*(?=$|[\\s).,!?\\]])/g, '$1<i>$2</i>');\n  s = s.replace(/__([^_]+)__/g, '<u>$1</u>');\n  s = s.replace(/~~([^~]+)~~/g, '<s>$1</s>');\n  // headers \u2192 bold + newline\n  s = s.replace(/^(#{1,6})\\s+(.+)$/gm, (m, hashes, content)=>`<b>${content.trim()}</b>\\n`);\n  // list markers \u2192 bullet\n  s = s.replace(/^[\\-\\*\\+]\\s+(.+)$/gm, '\u2022 $1');\n  // [text](url)\n  s = s.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (m,txt,url)=>{\n    const safe = sanitizeHref(url);\n    return safe ? `<a href=\"${safe}\">${txt}</a>` : txt;\n  });\n  return s;\n}\n\n/* ---------- 2) Protect <pre>/<code> contents ---------- */\nfunction protectPreCodeBlocks(input){\n  const placeholders = [];\n  let out = input;\n\n  out = out.replace(/<pre\\b[^>]*>([\\s\\S]*?)<\\/pre>/gi, (m, inner)=>{\n    const token = `__PRE_BLOCK_${placeholders.length}__`;\n    placeholders.push({token, tag:'pre', content: escapeHtml(inner)});\n    return token;\n  });\n\n  out = out.replace(/<code\\b[^>]*>([\\s\\S]*?)<\\/code>/gi, (m, inner)=>{\n    const token = `__CODE_BLOCK_${placeholders.length}__`;\n    placeholders.push({token, tag:'code', content: escapeHtml(inner)});\n    return token;\n  });\n\n  const restore = (s)=>{\n    for (const ph of placeholders){\n      s = s.split(ph.token).join(`<${ph.tag}>${ph.content}</${ph.tag}>`);\n    }\n    return s;\n  };\n\n  return { text: out, restore };\n}\n\n/* ---------- 3) HTML normalize ---------- */\nfunction htmlNormalize(input){\n  let s = input;\n\n  // Convert ALL <br> to newlines for Telegram HTML\n  s = s.replace(/<br\\s*\\/?>/gi, '\\n');\n\n  // lists\n  s = s.replace(/<li[^>]*>/gi, '\u2022 ').replace(/<\\/li>/gi, '\\n');\n  s = s.replace(/<\\/?(ul|ol)[^>]*>/gi, '');\n\n  // paragraphs/divs \u2192 newlines\n  s = s.replace(/<p[^>]*>/gi, '').replace(/<\\/p>/gi, '\\n\\n');\n  s = s.replace(/<div[^>]*>/gi, '').replace(/<\\/div>/gi, '\\n');\n\n  // headers \u2192 <b> + newline\n  s = s.replace(/<h[1-6][^>]*>([\\s\\S]*?)<\\/h[1-6]>/gi, (m, inner)=>`<b>${inner.trim()}</b>\\n`);\n\n  // Map aliases -> canonical\n  s = s.replace(/<\\/?strong\\b/gi, t=>t.replace(/strong/i,'b'));\n  s = s.replace(/<\\/?em\\b/gi, t=>t.replace(/em/i,'i'));\n  s = s.replace(/<\\/?ins\\b/gi, t=>t.replace(/ins/i,'u'));\n  s = s.replace(/<\\/?(strike|del)\\b/gi, t=>t.replace(/strike|del/i,'s'));\n\n  // remove spans\n  s = s.replace(/<\\/?span[^>]*>/gi, '');\n\n  // images -> textual hint\n  s = s.replace(/<img[^>]*src\\s*=\\s*(\"[^\"]+\"|'[^']+'|[^\\s>]+)[^>]*>/gi, (m,src)=>{\n    const clean = String(src).replace(/^['\"]|['\"]$/g,'');\n    return ` (\u1ea3nh: ${clean}) `;\n  });\n\n  return s;\n}\n\n/* ---------- 3.5) Escape lone '<' that are NOT valid tags ---------- */\nfunction escapeLoneAngles(input){\n  // allow only tags in: a|b|i|u|s|code|pre|blockquote  (NO br)\n  return input.replace(/<(?!\\/?(a|b|i|u|s|code|pre|blockquote)\\b)/gi, '&lt;');\n}\n\n/* ---------- 4) Sanitize tags (keep only Telegram tags) ---------- */\nfunction sanitizeHtml(input){\n  return input.replace(/<\\/?([a-z0-9]+)(\\s+[^>]*)?>/gi, (full, tag, attrs='')=>{\n    const t = tag.toLowerCase();\n\n    if (t === 'a'){\n      if (full.startsWith('</')) return '</a>';\n      const hrefMatch = attrs.match(/href\\s*=\\s*(\"[^\"]+\"|'[^']+'|[^\\s>]+)/i);\n      const safeHref = hrefMatch ? sanitizeHref(hrefMatch[1]) : '';\n      return safeHref ? `<a href=\"${safeHref}\">` : '';\n    }\n\n    if (!allowed.has(t)) return ''; // drop unsupported\n    return full[1] === '/' ? `</${t}>` : `<${t}>`;\n  });\n}\n\n/* ---------- 5) Track ONLY long-lived tags across chunks ---------- */\n/* We only track <a>, <pre>, <blockquote> so inline pairs never get auto-closed. */\nfunction getOpenTagsWithAttrs(html){\n  const trackable = new Set(['a','pre','blockquote']);\n  const stack = [];\n  const re = /<\\/?([a-z0-9]+)(?:\\s+[^>]*)?>/gi;\n  let m;\n  while ((m = re.exec(html))){\n    const raw = m[0];\n    const t = m[1].toLowerCase();\n    if (!trackable.has(t)) continue;\n\n    if (raw[1] === '/'){\n      const idx = [...stack].reverse().findIndex(e=>e.tag===t);\n      if (idx !== -1) stack.splice(stack.length - 1 - idx, 1);\n    } else {\n      if (t === 'a'){\n        const hrefMatch = raw.match(/href\\s*=\\s*(\"[^\"]+\"|'[^']+'|[^\\s>]+)/i);\n        const href = hrefMatch ? sanitizeHref(hrefMatch[1]) : '';\n        if (href) stack.push({tag:'a', href});\n      } else {\n        stack.push({tag:t});\n      }\n    }\n  }\n  return stack;\n}\nconst closeTags   = (open)=>[...open].reverse().map(e=>`</${e.tag}>`).join('');\nconst openTagsStr = (open)=>open.map(e=>e.tag==='a'?`<a href=\"${e.href}\">`:`<${e.tag}>`).join('');\n\n/* ---------- 6) Normalize pipeline ---------- */\nfunction normalizeToTelegramHtml(input){\n  const looksLikeMd = /(^|\\s)[*_`~]|^#{1,6}\\s|```/.test(input);\n  let s = looksLikeMd ? mdToHtml(input) : input;\n\n  s = htmlNormalize(s);\n\n  const protector = protectPreCodeBlocks(s);\n  s = protector.text;\n\n  // avoid \"<10\" etc. being treated as a tag\n  s = escapeLoneAngles(s);\n\n  s = sanitizeHtml(s);\n  s = protector.restore(s);\n\n  // collapse multiple newlines\n  s = s.replace(/\\n{3,}/g, '\\n\\n').trim();\n  return s;\n}\n\n/* ---------- 7) Split safely ---------- */\nfunction splitHtmlSmart(html, maxLen){\n  // Atomic segments: a, pre, blockquote, and inline pairs b/i/u/s/code\n  const re = /(<a\\b[^>]*>[\\s\\S]*?<\\/a>)|(<pre\\b[^>]*>[\\s\\S]*?<\\/pre>)|(<blockquote\\b[^>]*>[\\s\\S]*?<\\/blockquote>)|(<b\\b[^>]*>[\\s\\S]*?<\\/b>)|(<i\\b[^>]*>[\\s\\S]*?<\\/i>)|(<u\\b[^>]*>[\\s\\S]*?<\\/u>)|(<s\\b[^>]*>[\\s\\S]*?<\\/s>)|(<code\\b[^>]*>[\\s\\S]*?<\\/code>)|(<[^>]+>)|([^<]+)/gi;\n\n  const segments = [];\n  let m;\n  while ((m = re.exec(html))){\n    segments.push(\n      m[1] || m[2] || m[3] || m[4] || m[5] || m[6] || m[7] || m[8] ||\n      m[9] || m[10] // raw tag (will be sanitized already) or plain text (may contain \\n)\n    );\n  }\n\n  const chunks = [];\n  let buffer = '';\n  let prefixOpen = '';\n\n  const pushBuffer = ()=>{\n    if (!buffer.trim()) { buffer = ''; return; }\n    let out = prefixOpen + buffer;\n    // Only close a/pre/blockquote\n    const open = getOpenTagsWithAttrs(out);\n    out += closeTags(open);\n    prefixOpen = openTagsStr(open); // reopen at next chunk if any\n    chunks.push(out.trim());\n    buffer = '';\n  };\n\n  // Shrink visible text inside <a> if needed\n  const shrinkAnchorIfTooLong = (seg, room)=>{\n    const mm = /^<a\\b[^>]*>([\\s\\S]*?)<\\/a>$/i.exec(seg);\n    if (!mm) return seg;\n    const visible = mm[1];\n    if (seg.length <= room) return seg;\n    const truncated = (visible.length > room - 30) ? (visible.slice(0, Math.max(3, room - 33)) + '...') : visible;\n    return seg.replace(visible, truncated);\n  };\n\n  // Split huge <pre> / <blockquote> into multiple wrapped chunks\n  const splitWrappedBlock = (segTag, seg, available)=>{\n    const rx = new RegExp(`^<${segTag}[^>]*>([\\\\s\\\\S]*)<\\\\/${segTag}>$`, 'i');\n    const mm = rx.exec(seg);\n    if (!mm) return false;\n    let content = mm[1];\n    const shellLen = (`<${segTag}></${segTag}>`).length;\n\n    if (seg.length <= available) return false;\n\n    if (buffer.trim()) pushBuffer();\n\n    while (content.length){\n      const room = Math.max(200, maxLen - prefixOpen.length - shellLen - 10);\n      const slice = content.slice(0, room);\n      buffer = `<${segTag}>${slice}</${segTag}>`;\n      pushBuffer();\n      content = content.slice(slice.length);\n    }\n    return true;\n  };\n\n  for (const seg of segments){\n    const segLen = seg.length;\n\n    // if appending seg would overflow\n    if ((prefixOpen.length + buffer.length + segLen) > maxLen){\n\n      // Try shrinking anchor text\n      if (/^<a\\b/i.test(seg)){\n        const room = maxLen - prefixOpen.length - buffer.length - 1;\n        const shrunk = shrinkAnchorIfTooLong(seg, Math.max(60, room));\n        if ((prefixOpen.length + buffer.length + shrunk.length) <= maxLen){\n          buffer += shrunk;\n          continue;\n        }\n      }\n\n      // Split huge <pre>/<blockquote>\n      const available = maxLen - prefixOpen.length - buffer.length;\n      if (/^<pre\\b/i.test(seg) && splitWrappedBlock('pre', seg, available)) continue;\n      if (/^<blockquote\\b/i.test(seg) && splitWrappedBlock('blockquote', seg, available)) continue;\n\n      // Push current chunk\n      pushBuffer();\n\n      // Place seg into new buffer or split plain text / hard-cut tag as last resort\n      if ((prefixOpen.length + segLen) <= maxLen){\n        buffer = seg;\n      } else if (!seg.startsWith('<')) {\n        // split plain text by whitespace/newlines then hard-cut if needed\n        const words = seg.split(/(\\s+)/);\n        for (const w of words){\n          const candidate = buffer + w;\n          if ((prefixOpen.length + candidate.length) > maxLen){\n            pushBuffer();\n            if ((prefixOpen.length + w.length) > maxLen){\n              let s = w;\n              const room = Math.max(200, maxLen - prefixOpen.length - 10);\n              while (s.length){\n                buffer = s.slice(0, room);\n                pushBuffer();\n                s = s.slice(room);\n              }\n            } else {\n              buffer = w;\n            }\n          } else {\n            buffer = candidate;\n          }\n        }\n      } else {\n        // FINAL FALLBACK: extremely long single tag (rare)\n        let start = 0;\n        const room = Math.max(200, maxLen - prefixOpen.length - 10);\n        while (start < segLen){\n          buffer = seg.slice(start, start + room);\n          pushBuffer();\n          start += room;\n        }\n      }\n      continue;\n    }\n\n    // fits \u2192 append\n    buffer += seg;\n  }\n\n  if (buffer.trim()) pushBuffer();\n  return chunks;\n}\n\n/* ===== Run ===== */\nconst normalized = normalizeToTelegramHtml(text);\nconst chunks = splitHtmlSmart(normalized, MAX_LEN);\n\n/* Export to Telegram node */\nreturn chunks.map(c => ({ json: { text: c } }));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "7e9cff6c-0a6c-47c9-9993-c35153a4ed60",
      "name": "Clean and Chunk",
      "type": "n8n-nodes-base.code",
      "position": [
        -3680,
        -944
      ],
      "parameters": {
        "jsCode": "/**\n * Telegram HTML Normalizer + Chunker (\u2264 2000 chars)\n * - Markdown \u2192 Telegram HTML (a,b,i,u,s,code,pre,blockquote)  [NO <br>]\n * - Map/strip unsupported tags, sanitize attributes\n * - Convert all <br> to '\\n' (Telegram doesn't support <br>)\n * - Preserve links/code/pre; do NOT split inside <a>/<pre>/<blockquote> and inline pairs\n * - Close/reopen ONLY long-lived tags per chunk (a, pre, blockquote)\n * - Prevent stray closing tags and treat lone \"<\" safely\n */\n\nconst MAX_LEN = 2000;\n\n/* === Get input === */\nlet text = ($input.first().json.output ?? $input.first().json.text ?? '').trim();\nif (!text) return [];\n\n/* === Allowed Telegram tags (NO <br>) === */\nconst allowed = new Set(['a','b','i','u','s','code','pre','blockquote']);\n\n/* ---------- Utils ---------- */\nconst escapeHtml = (s) => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\n\nfunction sanitizeHref(href){\n  if (!href) return '';\n  const h = String(href).trim().replace(/^['\"]|['\"]$/g,'');\n  const lower = h.toLowerCase();\n  if (lower.startsWith('javascript:') || lower.startsWith('data:')) return '';\n  return h;\n}\n\n/* ---------- 1) Markdown -> HTML ---------- */\nfunction mdToHtml(md){\n  let s = md;\n  // code block\n  s = s.replace(/```([\\s\\S]*?)```/g, (m,p1)=>`<pre>${escapeHtml(p1.trim())}</pre>`);\n  // inline code\n  s = s.replace(/`([^`]+)`/g, (m,p1)=>`<code>${escapeHtml(p1)}</code>`);\n  // bold / italic / underline / strike\n  s = s.replace(/\\*\\*([^*]+)\\*\\*/g, '<b>$1</b>');\n  s = s.replace(/(^|[\\s(])\\*([^*\\n]+)\\*(?=$|[\\s).,!?\\]])/g, '$1<i>$2</i>');\n  s = s.replace(/__([^_]+)__/g, '<u>$1</u>');\n  s = s.replace(/~~([^~]+)~~/g, '<s>$1</s>');\n  // headers \u2192 bold + newline\n  s = s.replace(/^(#{1,6})\\s+(.+)$/gm, (m, hashes, content)=>`<b>${content.trim()}</b>\\n`);\n  // list markers \u2192 bullet\n  s = s.replace(/^[\\-\\*\\+]\\s+(.+)$/gm, '\u2022 $1');\n  // [text](url)\n  s = s.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (m,txt,url)=>{\n    const safe = sanitizeHref(url);\n    return safe ? `<a href=\"${safe}\">${txt}</a>` : txt;\n  });\n  return s;\n}\n\n/* ---------- 2) Protect <pre>/<code> contents ---------- */\nfunction protectPreCodeBlocks(input){\n  const placeholders = [];\n  let out = input;\n\n  out = out.replace(/<pre\\b[^>]*>([\\s\\S]*?)<\\/pre>/gi, (m, inner)=>{\n    const token = `__PRE_BLOCK_${placeholders.length}__`;\n    placeholders.push({token, tag:'pre', content: escapeHtml(inner)});\n    return token;\n  });\n\n  out = out.replace(/<code\\b[^>]*>([\\s\\S]*?)<\\/code>/gi, (m, inner)=>{\n    const token = `__CODE_BLOCK_${placeholders.length}__`;\n    placeholders.push({token, tag:'code', content: escapeHtml(inner)});\n    return token;\n  });\n\n  const restore = (s)=>{\n    for (const ph of placeholders){\n      s = s.split(ph.token).join(`<${ph.tag}>${ph.content}</${ph.tag}>`);\n    }\n    return s;\n  };\n\n  return { text: out, restore };\n}\n\n/* ---------- 3) HTML normalize ---------- */\nfunction htmlNormalize(input){\n  let s = input;\n\n  // Convert ALL <br> to newlines for Telegram HTML\n  s = s.replace(/<br\\s*\\/?>/gi, '\\n');\n\n  // lists\n  s = s.replace(/<li[^>]*>/gi, '\u2022 ').replace(/<\\/li>/gi, '\\n');\n  s = s.replace(/<\\/?(ul|ol)[^>]*>/gi, '');\n\n  // paragraphs/divs \u2192 newlines\n  s = s.replace(/<p[^>]*>/gi, '').replace(/<\\/p>/gi, '\\n\\n');\n  s = s.replace(/<div[^>]*>/gi, '').replace(/<\\/div>/gi, '\\n');\n\n  // headers \u2192 <b> + newline\n  s = s.replace(/<h[1-6][^>]*>([\\s\\S]*?)<\\/h[1-6]>/gi, (m, inner)=>`<b>${inner.trim()}</b>\\n`);\n\n  // Map aliases -> canonical\n  s = s.replace(/<\\/?strong\\b/gi, t=>t.replace(/strong/i,'b'));\n  s = s.replace(/<\\/?em\\b/gi, t=>t.replace(/em/i,'i'));\n  s = s.replace(/<\\/?ins\\b/gi, t=>t.replace(/ins/i,'u'));\n  s = s.replace(/<\\/?(strike|del)\\b/gi, t=>t.replace(/strike|del/i,'s'));\n\n  // remove spans\n  s = s.replace(/<\\/?span[^>]*>/gi, '');\n\n  // images -> textual hint\n  s = s.replace(/<img[^>]*src\\s*=\\s*(\"[^\"]+\"|'[^']+'|[^\\s>]+)[^>]*>/gi, (m,src)=>{\n    const clean = String(src).replace(/^['\"]|['\"]$/g,'');\n    return ` (\u1ea3nh: ${clean}) `;\n  });\n\n  return s;\n}\n\n/* ---------- 3.5) Escape lone '<' that are NOT valid tags ---------- */\nfunction escapeLoneAngles(input){\n  // allow only tags in: a|b|i|u|s|code|pre|blockquote  (NO br)\n  return input.replace(/<(?!\\/?(a|b|i|u|s|code|pre|blockquote)\\b)/gi, '&lt;');\n}\n\n/* ---------- 4) Sanitize tags (keep only Telegram tags) ---------- */\nfunction sanitizeHtml(input){\n  return input.replace(/<\\/?([a-z0-9]+)(\\s+[^>]*)?>/gi, (full, tag, attrs='')=>{\n    const t = tag.toLowerCase();\n\n    if (t === 'a'){\n      if (full.startsWith('</')) return '</a>';\n      const hrefMatch = attrs.match(/href\\s*=\\s*(\"[^\"]+\"|'[^']+'|[^\\s>]+)/i);\n      const safeHref = hrefMatch ? sanitizeHref(hrefMatch[1]) : '';\n      return safeHref ? `<a href=\"${safeHref}\">` : '';\n    }\n\n    if (!allowed.has(t)) return ''; // drop unsupported\n    return full[1] === '/' ? `</${t}>` : `<${t}>`;\n  });\n}\n\n/* ---------- 5) Track ONLY long-lived tags across chunks ---------- */\n/* We only track <a>, <pre>, <blockquote> so inline pairs never get auto-closed. */\nfunction getOpenTagsWithAttrs(html){\n  const trackable = new Set(['a','pre','blockquote']);\n  const stack = [];\n  const re = /<\\/?([a-z0-9]+)(?:\\s+[^>]*)?>/gi;\n  let m;\n  while ((m = re.exec(html))){\n    const raw = m[0];\n    const t = m[1].toLowerCase();\n    if (!trackable.has(t)) continue;\n\n    if (raw[1] === '/'){\n      const idx = [...stack].reverse().findIndex(e=>e.tag===t);\n      if (idx !== -1) stack.splice(stack.length - 1 - idx, 1);\n    } else {\n      if (t === 'a'){\n        const hrefMatch = raw.match(/href\\s*=\\s*(\"[^\"]+\"|'[^']+'|[^\\s>]+)/i);\n        const href = hrefMatch ? sanitizeHref(hrefMatch[1]) : '';\n        if (href) stack.push({tag:'a', href});\n      } else {\n        stack.push({tag:t});\n      }\n    }\n  }\n  return stack;\n}\nconst closeTags   = (open)=>[...open].reverse().map(e=>`</${e.tag}>`).join('');\nconst openTagsStr = (open)=>open.map(e=>e.tag==='a'?`<a href=\"${e.href}\">`:`<${e.tag}>`).join('');\n\n/* ---------- 6) Normalize pipeline ---------- */\nfunction normalizeToTelegramHtml(input){\n  const looksLikeMd = /(^|\\s)[*_`~]|^#{1,6}\\s|```/.test(input);\n  let s = looksLikeMd ? mdToHtml(input) : input;\n\n  s = htmlNormalize(s);\n\n  const protector = protectPreCodeBlocks(s);\n  s = protector.text;\n\n  // avoid \"<10\" etc. being treated as a tag\n  s = escapeLoneAngles(s);\n\n  s = sanitizeHtml(s);\n  s = protector.restore(s);\n\n  // collapse multiple newlines\n  s = s.replace(/\\n{3,}/g, '\\n\\n').trim();\n  return s;\n}\n\n/* ---------- 7) Split safely ---------- */\nfunction splitHtmlSmart(html, maxLen){\n  // Atomic segments: a, pre, blockquote, and inline pairs b/i/u/s/code\n  const re = /(<a\\b[^>]*>[\\s\\S]*?<\\/a>)|(<pre\\b[^>]*>[\\s\\S]*?<\\/pre>)|(<blockquote\\b[^>]*>[\\s\\S]*?<\\/blockquote>)|(<b\\b[^>]*>[\\s\\S]*?<\\/b>)|(<i\\b[^>]*>[\\s\\S]*?<\\/i>)|(<u\\b[^>]*>[\\s\\S]*?<\\/u>)|(<s\\b[^>]*>[\\s\\S]*?<\\/s>)|(<code\\b[^>]*>[\\s\\S]*?<\\/code>)|(<[^>]+>)|([^<]+)/gi;\n\n  const segments = [];\n  let m;\n  while ((m = re.exec(html))){\n    segments.push(\n      m[1] || m[2] || m[3] || m[4] || m[5] || m[6] || m[7] || m[8] ||\n      m[9] || m[10] // raw tag (will be sanitized already) or plain text (may contain \\n)\n    );\n  }\n\n  const chunks = [];\n  let buffer = '';\n  let prefixOpen = '';\n\n  const pushBuffer = ()=>{\n    if (!buffer.trim()) { buffer = ''; return; }\n    let out = prefixOpen + buffer;\n    // Only close a/pre/blockquote\n    const open = getOpenTagsWithAttrs(out);\n    out += closeTags(open);\n    prefixOpen = openTagsStr(open); // reopen at next chunk if any\n    chunks.push(out.trim());\n    buffer = '';\n  };\n\n  // Shrink visible text inside <a> if needed\n  const shrinkAnchorIfTooLong = (seg, room)=>{\n    const mm = /^<a\\b[^>]*>([\\s\\S]*?)<\\/a>$/i.exec(seg);\n    if (!mm) return seg;\n    const visible = mm[1];\n    if (seg.length <= room) return seg;\n    const truncated = (visible.length > room - 30) ? (visible.slice(0, Math.max(3, room - 33)) + '...') : visible;\n    return seg.replace(visible, truncated);\n  };\n\n  // Split huge <pre> / <blockquote> into multiple wrapped chunks\n  const splitWrappedBlock = (segTag, seg, available)=>{\n    const rx = new RegExp(`^<${segTag}[^>]*>([\\\\s\\\\S]*)<\\\\/${segTag}>$`, 'i');\n    const mm = rx.exec(seg);\n    if (!mm) return false;\n    let content = mm[1];\n    const shellLen = (`<${segTag}></${segTag}>`).length;\n\n    if (seg.length <= available) return false;\n\n    if (buffer.trim()) pushBuffer();\n\n    while (content.length){\n      const room = Math.max(200, maxLen - prefixOpen.length - shellLen - 10);\n      const slice = content.slice(0, room);\n      buffer = `<${segTag}>${slice}</${segTag}>`;\n      pushBuffer();\n      content = content.slice(slice.length);\n    }\n    return true;\n  };\n\n  for (const seg of segments){\n    const segLen = seg.length;\n\n    // if appending seg would overflow\n    if ((prefixOpen.length + buffer.length + segLen) > maxLen){\n\n      // Try shrinking anchor text\n      if (/^<a\\b/i.test(seg)){\n        const room = maxLen - prefixOpen.length - buffer.length - 1;\n        const shrunk = shrinkAnchorIfTooLong(seg, Math.max(60, room));\n        if ((prefixOpen.length + buffer.length + shrunk.length) <= maxLen){\n          buffer += shrunk;\n          continue;\n        }\n      }\n\n      // Split huge <pre>/<blockquote>\n      const available = maxLen - prefixOpen.length - buffer.length;\n      if (/^<pre\\b/i.test(seg) && splitWrappedBlock('pre', seg, available)) continue;\n      if (/^<blockquote\\b/i.test(seg) && splitWrappedBlock('blockquote', seg, available)) continue;\n\n      // Push current chunk\n      pushBuffer();\n\n      // Place seg into new buffer or split plain text / hard-cut tag as last resort\n      if ((prefixOpen.length + segLen) <= maxLen){\n        buffer = seg;\n      } else if (!seg.startsWith('<')) {\n        // split plain text by whitespace/newlines then hard-cut if needed\n        const words = seg.split(/(\\s+)/);\n        for (const w of words){\n          const candidate = buffer + w;\n          if ((prefixOpen.length + candidate.length) > maxLen){\n            pushBuffer();\n            if ((prefixOpen.length + w.length) > maxLen){\n              let s = w;\n              const room = Math.max(200, maxLen - prefixOpen.length - 10);\n              while (s.length){\n                buffer = s.slice(0, room);\n                pushBuffer();\n                s = s.slice(room);\n              }\n            } else {\n              buffer = w;\n            }\n          } else {\n            buffer = candidate;\n          }\n        }\n      } else {\n        // FINAL FALLBACK: extremely long single tag (rare)\n        let start = 0;\n        const room = Math.max(200, maxLen - prefixOpen.length - 10);\n        while (start < segLen){\n          buffer = seg.slice(start, start + room);\n          pushBuffer();\n          start += room;\n        }\n      }\n      continue;\n    }\n\n    // fits \u2192 append\n    buffer += seg;\n  }\n\n  if (buffer.trim()) pushBuffer();\n  return chunks;\n}\n\n/* ===== Run ===== */\nconst normalized = normalizeToTelegramHtml(text);\nconst chunks = splitHtmlSmart(normalized, MAX_LEN);\n\n/* Export to Telegram node */\nreturn chunks.map(c => ({ json: { text: c } }));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "2ca2aa77-8aac-4b10-90ef-c50f761dd815",
      "name": "Send Typing Action",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -3824,
        -352
      ],
      "parameters": {
        "chatId": "={{ $('Telegram Trigger - User Message').item.json.message.from.id }}",
        "operation": "sendChatAction"
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "2829e9d4-368d-4c61-b22b-795d03fc0064",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4960,
        -1456
      ],
      "parameters": {
        "color": 7,
        "width": 1584,
        "height": 800,
        "content": "## 1. Daily Pulse Automation  \n\n- **Schedule Trigger:**  Runs every morning at a set time (e.g., 8:00 AM). This reflects Toan\u2019s philosophy of keeping automations consistent and reliable.  \n- **Reddit & n8n Forum Search + Merge Sources:**  Fetches trending posts from Reddit and the n8n Forum. The merging logic was crafted by Nguy\u1ec5n Thi\u1ec7u To\u00e0n to unify multiple community voices into one feed.  \n- **AI Summarizer Overview:**  Summarizes the hottest discussions into concise insights\u2014designed with Toan\u2019s emphasis on clarity and usefulness.  \n- **Format for Telegram & Send Auto Reply:**  Splits summaries to fit Telegram\u2019s limits and delivers them seamlessly. This attention to detail reflects Toan\u2019s commitment to great user experience.  "
      },
      "typeVersion": 1
    },
    {
      "id": "d5f62000-a125-4c1e-9cfe-62dc9da93d22",
      "name": "Wrap as Data Object2",
      "type": "n8n-nodes-base.code",
      "position": [
        -4016,
        -1168
      ],
      "parameters": {
        "jsCode": "return [\n  {\n    json: {\n      data: items.map(item => item.json)\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "55e22c80-a20f-42a9-9269-a2e3c43a51d0",
      "name": "Set Memory ID Session",
      "type": "n8n-nodes-base.set",
      "position": [
        -3808,
        -1168
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a4d9d186-0b7e-4e65-aecf-91f400e826d8",
              "name": "MyTelegramID",
              "type": "number",
              "value": 6163095869
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7f388361-c84e-48ee-889d-5b0b95c1cfff",
      "name": "Send a text message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -3024,
        -1152
      ],
      "parameters": {
        "text": "=<i>\u0110ang t\u00ecm ki\u1ebfm tr\u00ean: {{ $json.output.platforms }} \n- Keyword: <b>{{ $json.output.keyword }}</b>\n- Redit: {{ $json.output.reddit_sort }}, {{ $json.output.reddit_time }}\n- n8n Community: {{ $json.output.n8ncom_sort }}, {{ $json.output.n8ncom_time }} </i>",
        "chatId": "={{ $('Telegram Trigger - User Message').item.json.message.from.id }}",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "bb7c34cf-c316-4b0a-9127-77cf1f9783c8",
      "name": "Send a text message1",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -3136,
        288
      ],
      "parameters": {
        "text": "=<i>\u0110ang truy c\u1eadp v\u00e0o {{ $json.output.link_url }} \u0111\u1ec3 \u0111\u1ecdc d\u1eef li\u1ec7u</i>",
        "chatId": "={{ $('Telegram Trigger - User Message').item.json.message.from.id }}",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "5343abec-1bfa-45bf-9dc0-96bee2668e3e",
      "name": "Google Gemini Chat Model jaynguyena01",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -1248,
        112
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "036fa9e2-83c5-487a-aac8-f85e3b56b834",
      "name": "Groq Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "position": [
        -1408,
        64
      ],
      "parameters": {
        "model": "openai/gpt-oss-120b",
        "options": {}
      },
      "credentials": {
        "groqApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8d4835f6-44ce-4bb5-a230-addc393f618b",
      "name": "Call 'Auto-post Facebook \"The n8n Insider\"'",
      "type": "n8n-nodes-base.executeWorkflow",
      "position": [
        -1328,
        416
      ],
      "parameters": {
        "options": {},
        "workflowId": {
          "__rl": true,
          "mode": "list",
          "value": "RGu53HJnureId8NU",
          "cachedResultUrl": "/workflow/RGu53HJnureId8NU",
          "cachedResultName": "(Active) Auto-post Facebook \"The n8n Insider\""
        },
        "workflowInputs": {
          "value": {
            "n8n_forum_topic_update": "={{JSON.stringify( $json) }}"
          },
          "schema": [
            {
              "id": "n8n_new_version_info",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "n8n_new_version_info",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "n8n_forum_topic_update",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "n8n_forum_topic_update",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "66135fc4-baca-4a46-a938-e36d91eccc8a",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1456,
        -1456
      ],
      "parameters": {
        "color": 3,
        "width": 864,
        "height": 320,
        "content": "## \u2615BUY ME A COFFEE?\nHi! I am  **Nguy\u1ec5n Thi\u1ec7u To\u00e0n (or you can call me Jay / Jay Nguyen)** - a Verified n8n Creator. Thank you for using this template!\nThis workflow is shared with you for free. If it brings value to your work or saves you time, you can buy me a coffee as a small token of appreciation here:  **[My Donate Website](https://nguyenthieutoan.com/payment/)** *(PayPal or Momo)*\n\n*You can get more my useful workflow:* **[Click here](https://n8n.io/creators/nguyenthieutoan/)**"
      },
      "typeVersion": 1
    },
    {
      "id": "518f2680-4fe1-4daa-933b-fe2a7ea57236",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1456,
        -1072
      ],
      "parameters": {
        "color": 5,
        "width": 864,
        "height": 400,
        "content": "### \ud83d\udccc More Useful Workflows from Nguyen Thieu Toan (Jay Nguyen)\n- [Search Flights via Telegram + SerpAPI Price Alerts](https://n8n.io/workflows/12971): Automates flight search with Gemini and sends price alerts directly to Telegram.\n- [Smart Message Batching Messenger Chatbot](https://n8n.io/workflows/9192-smart-message-batching-ai-powered-facebook-messenger-chatbot-use-data-table/): Uses Data Tables to batch and manage AI-powered chatbot messages efficiently.\n- [Build a Messenger Customer Service Chatbot with Google Gemini](https://n8n.io/workflows/13080): Create a customer service chatbot using Gemini AI, designed for handling complex queries.\n- [TikTok Video Downloader (No Watermark) - Telegram Bot](https://n8n.io/workflows/10001)  turns your Telegram bot into a TikTok video downloader.\n\n*More Free workflow: *[Click here](https://n8n.io/creators/nguyenthieutoan/)**"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Reddit": {
      "main": [
        [
          {
            "node": "Get Only Search Result1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Comment": {
      "main": [
        [
          {
            "node": "Get Comment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has time?": {
      "main": [
        [
          {
            "node": "Reddit Search page JSON (has time)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Reddit Search page (no time)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Platform?": {
      "main": [
        [
          {
            "node": "n8n Community Fetch JSON",
            "type": "main",
            "index": 0
          },
          {
            "node": "Has keyword?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Has keyword?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "n8n Community Fetch JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Reddit?": {
      "main": [
        [
          {
            "node": "Get Post Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Comment": {
      "main": [
        [
          {
            "node": "Comment Summary 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Comment1": {
      "main": [
        [
          {
            "node": "Comment Summary 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has keyword?": {
      "main": [
        [
          {
            "node": "Reddit Search page JSON (no keyword)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Has time?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Sources1": {
      "main": [
        [
          {
            "node": "Wrap as Data Object2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean and Chunk": {
      "main": [
        [
          {
            "node": "Send Auto Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Groq Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Detect User Intent",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Structured Output Parser",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "AI Summarizer Clarify",
            "type": "ai_languageModel",
            "index": 1
          },
          {
            "node": "AI Summarizer Overview",
            "type": "ai_languageModel",
            "index": 1
          }
        ]
      ]
    },
    "Branch by Intent": {
      "main": [
        [
          {
            "node": "Platform?",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send a text message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wrap as Data Object",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Open link Reddit or n8n Comunity?",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send a text message1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "AI Summarizer Clarify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean and Chunk2": {
      "main": [
        [
          {
            "node": "Send reply1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Post Content": {
      "main": [
        [
          {
            "node": "Comment",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Link Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Groq Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "AI Summarizer Deep-dive",
            "type": "ai_languageModel",
            "index": 1
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Reddit",
            "type": "main",
            "index": 0
          },
          {
            "node": "n8n Community Fetch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Comment Summary 1": {
      "main": [
        [
          {
            "node": "Merge Link Content",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Comment Summary 2": {
      "main": [
        [
          {
            "node": "Merge Link Content",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Get Topic Content": {
      "main": [
        [
          {
            "node": "Merge Link Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If n8n Community?": {
      "main": [
        [
          {
            "node": "Get Topic Content",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Comment1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect User Intent": {
      "main": [
        [
          {
            "node": "Branch by Intent",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Typing Action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Link Content": {
      "main": [
        [
          {
            "node": "Wrap as Data Object",
            "type": "main",
            "index": 0
          },
          {
            "node": "Call 'Auto-post Facebook \"The n8n Insider\"'",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Search Result": {
      "main": [
        [
          {
            "node": "Wrap as Data Object",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MongoDB Chat Memory": {
      "ai_memory": [
        [
          {
            "node": "Detect User Intent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Wrap as Data Object": {
      "main": [
        [
          {
            "node": "AI Summarizer Deep-dive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "n8n Community Fetch": {
      "main": [
        [
          {
            "node": "Get Only Search Result2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MongoDB Chat Memory1": {
      "ai_memory": [
        [
          {
            "node": "AI Summarizer Deep-dive",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "MongoDB Chat Memory2": {
      "ai_memory": [
        [
          {
            "node": "AI Summarizer Overview",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "MongoDB Chat Memory3": {
      "ai_memory": [
        [
          {
            "node": "AI Summarizer Clarify",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Wrap as Data Object2": {
      "main": [
        [
          {
            "node": "Set Memory ID Session",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Summarizer Clarify": {
      "main": [
        [
          {
            "node": "Clean and Chunk2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Memory ID Session": {
      "main": [
        [
          {
            "node": "AI Summarizer Overview",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Summarizer Overview": {
      "main": [
        [
          {
            "node": "Clean and Chunk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Summarizer Deep-dive": {
      "main": [
        [
          {
            "node": "Format for Telegram Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Only Search Result1": {
      "main": [
        [
          {
            "node": "Merge Sources1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Only Search Result2": {
      "main": [
        [
          {
            "node": "Merge Sources1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Detect User Intent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "n8n Community Fetch JSON": {
      "main": [
        [
          {
            "node": "Get Only n8n community Search Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "AI Summarizer Overview",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Detect User Intent",
            "type": "ai_languageModel",
            "index": 1
          }
        ]
      ]
    },
    "Google Gemini Chat Model2": {
      "ai_languageModel": [
        [
          {
            "node": "AI Summarizer Clarify",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Format for Telegram Output": {
      "main": [
        [
          {
            "node": "Send reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reddit Search page (no time)": {
      "main": [
        [
          {
            "node": "Get Only Reddit Search Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Only Reddit Search Result": {
      "main": [
        [
          {
            "node": "Merge Search Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger - User Message": {
      "main": [
        [
          {
            "node": "Detect User Intent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Open link Reddit or n8n Comunity?": {
      "main": [
        [
          {
            "node": "If Reddit?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "If n8n Community?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reddit Search page JSON (has time)": {
      "main": [
        [
          {
            "node": "Get Only Reddit Search Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Only n8n community Search Result": {
      "main": [
        [
          {
            "node": "Merge Search Result",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Reddit Search page JSON (no keyword)": {
      "main": [
        [
          {
            "node": "Get Only Reddit Search Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model jaynguyena01": {
      "ai_languageModel": [
        [
          {
            "node": "AI Summarizer Deep-dive",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  }
}