{
  "name": "Twitter Intelligence Analysis",
  "tags": [
    {
      "name": "Twitter"
    },
    {
      "name": "AI"
    },
    {
      "name": "Telegram"
    }
  ],
  "nodes": [
    {
      "id": "dd5e616a-9663-4c10-851b-758934fcb265",
      "name": "Filter New Tweets",
      "type": "n8n-nodes-base.filter",
      "position": [
        544,
        -464
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a996252d-86f6-45c3-847a-be067263c565",
              "operator": {
                "type": "dateTime",
                "operation": "after"
              },
              "leftValue": "={{ $json.isoDate }}",
              "rightValue": "={{ $('Config').first().json.last_update_time }}"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 2.2,
      "alwaysOutputData": false
    },
    {
      "id": "77aa830d-f4f5-41d0-9081-37ef8dfc21ac",
      "name": "Config",
      "type": "n8n-nodes-base.postgres",
      "position": [
        0,
        -464
      ],
      "parameters": {
        "query": "SELECT * FROM twitter_following_config WHERE id = 1;",
        "options": {},
        "operation": "executeQuery"
      },
      "retryOnFail": true,
      "typeVersion": 2.6,
      "alwaysOutputData": true
    },
    {
      "id": "d1675590-638c-43ea-bce8-64299c5c4433",
      "name": "Analyze Tweet with AI",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "position": [
        -208,
        208
      ],
      "parameters": {
        "text": "={{ $('Config').item.json.prompt }}\n\nHere is the [Input Tweet]\nTitle: {{ $json.title }}\nContent: {{ $json.content }}\nOriginal Link: {{ $json.link }}",
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "models/gemini-2.5-flash",
          "cachedResultName": "models/gemini-2.5-flash"
        },
        "options": {},
        "resource": "document"
      },
      "typeVersion": 1
    },
    {
      "id": "dc4a7a05-fe01-46a1-811e-fa0532481ee2",
      "name": "Parse AI Response",
      "type": "n8n-nodes-base.code",
      "position": [
        -16,
        208
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Get the raw, deeply nested string output from the AI node\nconst aiRawOutput = $json.content.parts[0].text;\n\n// Use a regular expression to extract the JSON string part enclosed in ```\nconst jsonMatch = aiRawOutput.match(/\\{[\\s\\S]*\\}/);\n\n// Check if a JSON string was successfully found\nif (jsonMatch && jsonMatch[0]) {\n  try {\n    // Parse the extracted pure JSON string into a real JSON object\n    const parsedData = JSON.parse(jsonMatch[0]);\n    \n    // Return the parsed object as the output of this node\n    return {\n      json: parsedData\n    };\n  } catch (error) {\n    // If parsing fails, return an error message\n    return { json: { error: \"Failed to parse JSON\", details: error.message } };\n  }\n}\n\n// If no JSON string is found, also return an error message\nreturn { json: { error: \"JSON block not found in AI output\" } };"
      },
      "retryOnFail": true,
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "b453644d-24be-4404-9869-97b3751df12c",
      "name": "Get Latest Timestamp",
      "type": "n8n-nodes-base.code",
      "position": [
        -144,
        -160
      ],
      "parameters": {
        "jsCode": "// Get all items passed from the previous node\nconst items = $input.all();\n\n// If there is no input data, return an empty array to stop the workflow here\nif (items.length === 0) {\n  return [];\n}\n\n// Use the reduce method to iterate through all items and find the latest date\n// We start with a very early date as the initial comparison value\nconst initialValue = { json: { isoDate: '1970-01-01T00:00:00.000Z' } };\n\nconst latestItem = items.reduce((latest, current) => {\n  // ISO format date strings can be compared directly\n  if (current.json.isoDate > latest.json.isoDate) {\n    return current;\n  } else {\n    return latest;\n  }\n}, initialValue);\n\n// Extract the isoDate from the found latest item\nconst latestTimestamp = latestItem.json.isoDate;\n\n// Return the result in the standard n8n format\n// This result can be easily used by subsequent nodes (like IF or Set nodes)\nreturn [\n  {\n    json: {\n      latestTimestamp: latestTimestamp\n    }\n  }\n];"
      },
      "retryOnFail": true,
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "6a9e35db-ddff-400c-9b7b-ecc61f7ceff3",
      "name": "Update Latest Timestamp",
      "type": "n8n-nodes-base.postgres",
      "position": [
        160,
        -160
      ],
      "parameters": {
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "twitter_following_config",
          "cachedResultName": "twitter_following_config"
        },
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public",
          "cachedResultName": "public"
        },
        "columns": {
          "value": {
            "id": "={{ $('Config').first().json.id }}",
            "last_update_time": "={{ $json.latestTimestamp }}"
          },
          "schema": [
            {
              "id": "id",
              "type": "number",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "id",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "twitter_account",
              "type": "string",
              "display": true,
              "removed": true,
              "required": true,
              "displayName": "twitter_account",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "last_update_time",
              "type": "dateTime",
              "display": true,
              "required": false,
              "displayName": "last_update_time",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "prompt",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "prompt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "note",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "note",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update"
      },
      "typeVersion": 2.6
    },
    {
      "id": "d7b10e6f-bb4a-4833-bc1e-feb47c3b496c",
      "name": "Check if New Tweets Exist",
      "type": "n8n-nodes-base.if",
      "position": [
        -464,
        -144
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "fe59cf26-d6fe-4d67-b6d3-e868bc4dd75f",
              "operator": {
                "type": "array",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{$('Filter New Tweets').all()}}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d837d13e-c9fd-4ecc-84d2-15aeeb2ebe3e",
      "name": "RSS",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        272,
        -464
      ],
      "parameters": {
        "url": "=[https://your-rsshub-instance.com/twitter/user/](https://your-rsshub-instance.com/twitter/user/){{ $json.twitter_account }}",
        "options": {}
      },
      "retryOnFail": true,
      "typeVersion": 1.2,
      "alwaysOutputData": true
    },
    {
      "id": "19e81953-f5fb-4e19-b71e-13b9827292db",
      "name": "Send Telegram Message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        352,
        208
      ],
      "parameters": {
        "text": "=\ud83d\udea8 Intelligence Level A\n\nTweet Analysis:\n{{ $json.summary }}\n\nKeywords: {{ $json.keywords.map(k => `#${k}`).join(' ') }}\n\nOriginal Link:\n{{ $json.link }}",
        "chatId": "YOUR_CHAT_ID",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "4d5ac869-e761-4fd7-98af-67c5110ca967",
      "name": "Filter by Level",
      "type": "n8n-nodes-base.filter",
      "position": [
        176,
        208
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "4be18a2a-4de2-422d-b5a0-875e63f925cc",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.level }}",
              "rightValue": "A"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "fd215a22-c9fa-402a-ae0a-69617d3bbb8e",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -464,
        -464
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "33ef18d6-6c7e-40d7-9a95-f2e8e6c0a154",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        -704
      ],
      "parameters": {
        "width": 208,
        "height": 400,
        "content": "## Trigger Setting\n\nSet the interval for automatic triggering (e.g., every 15 minutes)."
      },
      "typeVersion": 1
    },
    {
      "id": "931315cb-b601-4399-a417-bbb2c984aed7",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -272,
        -704
      ],
      "parameters": {
        "width": 448,
        "height": 400,
        "content": "## Read Config from Database\nA database table template might look like this:\n\n| id | twitter_account | last_update_time | prompt | note |\n| --- | --- | --- | --- | --- |\n| 1 | elonmusk | 2025-08-12 15:20:08+00 | Your AI prompt... | A note about this account |\n\n---\n\n- **id**: Primary key.\n- **twitter_account**: The Twitter handle to monitor (without the @).\n- **last_update_time**: The ISO Date of the last processed tweet.\n- **prompt**: The prompt for the AI to analyze the tweet.\n- **note**: A personal note for this entry."
      },
      "typeVersion": 1
    },
    {
      "id": "33d98bb3-2c42-4fbc-a5e4-e1eccf16f3e5",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        208,
        -704
      ],
      "parameters": {
        "height": 400,
        "content": "## RSSHub Feed\n\nThis node fetches tweets via an RSS feed. You'll need your own RSSHub instance.\n\nFor more info on the URL format, check the official docs: [RSSHub Guide](https://docs.rsshub.app/routes/social-media#x-twitter)"
      },
      "typeVersion": 1
    },
    {
      "id": "020b6d79-bf6c-47c4-b74a-5e83ce3e4607",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        -704
      ],
      "parameters": {
        "height": 400,
        "content": "## Filter New Tweets\n\nFilters tweets to only include new ones based on their publication date (IsoDate).\n\nThis node only returns tweets with an IsoDate that is after the `last_update_time` retrieved from the Config node."
      },
      "typeVersion": 1
    },
    {
      "id": "029b9e0a-c4cf-4625-bbb5-2f87fe27191b",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        -256
      ],
      "parameters": {
        "width": 208,
        "height": 240,
        "content": "## IF Node\n\nChecks if the 'Filter New Tweets' node produced any output. If it's empty, the workflow stops. If not, it proceeds."
      },
      "typeVersion": 1
    },
    {
      "id": "8e71aa08-d0d2-4a16-a8ea-d466c0afc5a3",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -256,
        -256
      ],
      "parameters": {
        "width": 592,
        "height": 240,
        "content": "Finds the latest timestamp among all new tweets and updates the timestamp in the database for the next run."
      },
      "typeVersion": 1
    },
    {
      "id": "7fb4a07f-0970-41f2-8203-bd996276fc8a",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -256,
        32
      ],
      "parameters": {
        "width": 800,
        "height": 352,
        "content": "## Analysis Pipeline\n\n1.  **Analyze Tweet with AI**: Sends the tweet content and a prompt (from the Config node) to the AI for analysis.\n2.  **Parse AI Response**: Extracts the JSON object from the AI's text response.\n3.  **Filter by Level**: Filters the results based on the analysis (e.g., only proceed if `level` is 'A').\n4.  **Send Telegram Message**: Sends the final, formatted analysis to a Telegram chat."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "RSS": {
      "main": [
        [
          {
            "node": "Filter New Tweets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "RSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter by Level": {
      "main": [
        [
          {
            "node": "Send Telegram Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter New Tweets": {
      "main": [
        [
          {
            "node": "Check if New Tweets Exist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Response": {
      "main": [
        [
          {
            "node": "Filter by Level",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Latest Timestamp": {
      "main": [
        [
          {
            "node": "Update Latest Timestamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Tweet with AI": {
      "main": [
        [
          {
            "node": "Parse AI Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if New Tweets Exist": {
      "main": [
        [
          {
            "node": "Get Latest Timestamp",
            "type": "main",
            "index": 0
          },
          {
            "node": "Analyze Tweet with AI",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  }
}