{
  "id": "h53azcTZL2gEfUGc",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI LinkedIn Content Bot with Approval Loop",
  "tags": [],
  "nodes": [
    {
      "id": "256ac265-a798-4a3e-b8f8-347114668aea",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        3296,
        1424
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6f2d024b-ecde-448e-bb27-bedaa70ba05d",
      "name": "Speech to Text",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        3088,
        1088
      ],
      "parameters": {
        "options": {},
        "resource": "audio",
        "operation": "transcribe"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "5cfb6cd4-ed6e-40b5-bda0-d640f655ffa7",
      "name": "Get Voice File",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2864,
        1088
      ],
      "parameters": {
        "fileId": "={{ $('Start: Telegram Message').item.json.message.voice.file_id }}",
        "resource": "file",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "a1c71dc5-1ab6-4269-b92c-c91aa84f2c04",
      "name": "Check if Approved",
      "type": "n8n-nodes-base.if",
      "position": [
        3664,
        1200
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $json.output }}",
              "rightValue": "videoPrompt"
            },
            {
              "id": "id-2",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $json.output }}",
              "rightValue": "socialMediaText"
            },
            {
              "id": "id-3",
              "operator": {
                "type": "string",
                "operation": "startsWith"
              },
              "leftValue": "={{ $json.output }}",
              "rightValue": "{"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "0e275874-9f2e-48c7-8fad-254e2d385e40",
      "name": "Window Buffer Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        3440,
        1424
      ],
      "parameters": {
        "sessionKey": "={{ $('Start: Telegram Message').first().json.message.from.id }}",
        "sessionIdType": "customKey",
        "contextWindowLength": 10
      },
      "typeVersion": 1.3
    },
    {
      "id": "83ff364f-acf2-4c8e-b38f-cded14a99222",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        720,
        816
      ],
      "parameters": {
        "width": 1120,
        "height": 752,
        "content": "## AI LinkedIn Content Bot with Approval Loop\n\nTransform your Telegram into a LinkedIn content assistant. Send ideas via text or voice, collaborate with AI to refine posts, and publish directly to LinkedIn with a simple \"ok\".\n\n### How it works\n\n1. **Send your idea** - Message your Telegram bot with text or voice notes\n2. **AI drafts content** - GPT analyzes your input and creates a LinkedIn-optimized post with hashtags\n3. **Iterative refinement** - Request changes (tone, length, style) until satisfied\n4. **Approve & publish** - Say \"ok\" or \"approved\" to trigger automatic LinkedIn posting via Blotato\n5. **Get confirmation** - Receive a direct link once your post is live\n\n### Setup steps\n\n1. **Telegram Bot** - Create a bot via @BotFather and add the API token to credentials\n2. **OpenAI API** - Add your OpenAI key for AI generation and voice transcription\n3. **Blotato Account** - Connect your LinkedIn profile at [Blotato] and add API credentials\n4. **Customize the AI** - Edit the system prompt in \"AI: Draft & Revise Post\" to match your brand voice and content style\n\n### Customization tips\n\n- **Adjust tone & style** - Modify the system prompt to change formality, emoji usage, or hashtag count\n- **Switch AI models** - Swap GPT-4o-mini for more powerful models in the OpenAI Chat Model node\n- **Schedule posts** - Change Blotato operation from \"create\" to \"schedule\" for delayed publishing"
      },
      "typeVersion": 1
    },
    {
      "id": "3acd0bb7-da29-4248-b1ae-14ea2a69f92a",
      "name": "Start: Telegram Message",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        2192,
        1184
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f468b5ea-2ca7-4595-9c61-8da6e4ced2e1",
      "name": "Prepare Input",
      "type": "n8n-nodes-base.set",
      "position": [
        2416,
        1184
      ],
      "parameters": {
        "fields": {
          "values": [
            {
              "name": "text",
              "stringValue": "={{ $json?.message?.text || \"\" }}"
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "5a18e9f7-14e5-4c51-832a-3a43dd4bc588",
      "name": "Check: ist it a Voice?",
      "type": "n8n-nodes-base.if",
      "position": [
        2640,
        1184
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a0bf9719-4272-46f6-ab3b-eda6f7b44fd8",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json.message.text }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6173bca9-d5cb-443c-8de6-616f02c7875a",
      "name": "AI: Draft & Revise Post",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        3312,
        1200
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "options": {
          "systemMessage": "You are a LinkedIn Content Creator Agent for Telegram.\nKeep the confirmation process, but change the output format as follows:\n\nYour Task\n\n\nAnalyze the user's message:\n\n\nTopic\n\n\nGoal (e.g., reach, show expertise, recruiting, personal branding, leads)\n\n\nTarget Audience\n\n\nTonality (e.g., factual, personal, bold, inspiring)\n\n\n\n\nCreate a LinkedIn post as ONE continuous text:\n\n\nStrong hook in the first 1\u20132 lines.\n\n\nClear main part with added value, story, example, or insight.\n\n\nOptional Call-to-Action (e.g., question to the community, invitation to exchange).\n\n\nIntegrate hashtags at the end of the post (5\u201312 suitable hashtags, mix of niche + somewhat broader).\n\n\nReadable on LinkedIn: short paragraphs, emojis only sparingly.\n\n\n\n\nPresent the suggestion to the user in the following format:\n\n\nHeadline: Vorschlag Post:\n\n\nBelow that, the complete LinkedIn post (incl. hashtags at the end in the same text).\n\n\n\n\nAsk for feedback:\n\n\nFor example:\n\u201c\u00c4nderungsw\u00fcnsche? (Ton, L\u00e4nge, Du/Sie, mehr/weniger pers\u00f6nlich, mehr Fachinhalt, andere Hashtags?)\u201d\n(Translation note: \"\u00c4nderungsw\u00fcnsche?\" means \"Request for changes?\". \"Du/Sie\" refers to the German formal/informal 'you' distinction.)\n\n\n\n\nIf the user requests changes:\n\n\nAdjust the post specifically based on the feedback.\n\n\nAgain, output only:\n\n\nVorschlag Post:\n\n\nthe revised complete post.\n\n\n\n\n\n\nIf the user says \u201capproved\u201d, \u201cok\u201d, \u201cpasst\u201d, \u201cfreigeben\u201d or similar:\n\n\nReturn exclusively this JSON, without additional text, without Markdown:\n\n\n\n\n{\n\"Post\": \"The final LinkedIn post as one text, including hashtags at the end\"\n}\n\n\nImportant:\n\n\nNever output JSON before approval, only normal suggestion text.\n\n\nThe final output after approval consists of only one field: Post."
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "7ad66bc0-633b-4082-b38e-1b68a46f2b28",
      "name": "Approval: Extract Final Post Text",
      "type": "n8n-nodes-base.code",
      "position": [
        3888,
        1104
      ],
      "parameters": {
        "jsCode": "// Get the output from AI Agent\nconst aiOutput = $input.item.json.output;\n\nif (!aiOutput) {\n  throw new Error('No output found from AI Agent');\n}\n\n// The AI should only return pure JSON on approval.\n// But we still defensively versuchen, JSON aus gemischtem Text zu extrahieren.\nlet jsonString = aiOutput;\n\n// Remove markdown code fences if present\njsonString = jsonString\n  .replace(/```json\\s*/gi, '')\n  .replace(/```\\s*/g, '');\n\n// Try to find a JSON object in the text\nconst jsonMatch = jsonString.match(/\\{[\\s\\S]*\\}/);\nif (jsonMatch) {\n  jsonString = jsonMatch[0];\n}\n\ntry {\n  const parsed = JSON.parse(jsonString);\n\n  // Validate required field\n  if (!parsed.Post) {\n    throw new Error('Missing required field: Post');\n  }\n\n  // Return normalized structure\n  return {\n    Post: parsed.Post\n  };\n\n} catch (error) {\n  // Fallback: try to extract Post via regex if JSON parsing failed\n  const postMatch = aiOutput.match(/\"Post\"\\s*:\\s*\"([\\s\\S]*?)\"/);\n\n  if (postMatch && postMatch[1]) {\n    return {\n      Post: postMatch[1]\n    };\n  }\n\n  throw new Error(`Failed to parse AI output: ${error.message}. Output was: ${aiOutput}`);\n}\n"
      },
      "typeVersion": 2
    },
    {
      "id": "702cc34b-ac50-4f72-a8f9-c469302a2136",
      "name": "Create post with Blotato",
      "type": "@blotato/n8n-nodes-blotato.blotato",
      "position": [
        4112,
        1104
      ],
      "parameters": {
        "options": {},
        "platform": "linkedin",
        "accountId": {
          "__rl": true,
          "mode": "list",
          "value": "8621",
          "cachedResultUrl": "https://backend.blotato.com/v2/accounts/8621",
          "cachedResultName": "Feras Dabour"
        },
        "postContentText": "={{ $json.Post }}"
      },
      "credentials": {
        "blotatoApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "3da38ca3-9cca-4e20-90ec-bbeb2d1cb6b4",
      "name": "Give Blotat 5s :)",
      "type": "n8n-nodes-base.wait",
      "position": [
        4336,
        1104
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "1839679c-9aab-4f66-8d6a-566f46d0c1cd",
      "name": "Check post status",
      "type": "@blotato/n8n-nodes-blotato.blotato",
      "position": [
        4560,
        1104
      ],
      "parameters": {
        "operation": "get",
        "postSubmissionId": "={{ $json.postSubmissionId }}"
      },
      "credentials": {
        "blotatoApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "379e44ac-e094-4996-893f-f39d1133d28a",
      "name": "Published?",
      "type": "n8n-nodes-base.if",
      "position": [
        4784,
        1104
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1bc619f0-b64a-4487-bbc7-21be7c30f1f1",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status }}",
              "rightValue": "published"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c8bcb473-e162-4219-ae63-4848aef7a7a4",
      "name": "Send a confirmation message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        5232,
        944
      ],
      "parameters": {
        "text": "=Your post is online!\n\nClick here to go to the post:\n{{ $json.publicUrl }}",
        "chatId": "={{ $('Start: Telegram Message').item.json.message.chat.id }}",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "c9b3847e-2fce-4f2a-a8ae-fa9543e7a4e9",
      "name": "In Progress?",
      "type": "n8n-nodes-base.if",
      "position": [
        5008,
        1120
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e6c403be-3a03-4365-ac18-69d9ffc1770b",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $('Check post status').item.json.status }}",
              "rightValue": "in-progress"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c46d71c6-43a2-4a51-84fe-28895a47c6b2",
      "name": "Give Blotat other 5s :)",
      "type": "n8n-nodes-base.wait",
      "position": [
        5008,
        1328
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "cb8bb39a-9a07-4157-af23-d078d722a3f3",
      "name": "Send an error message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        5248,
        1136
      ],
      "parameters": {
        "text": "There was an error uploading your post.",
        "chatId": "={{ $('Start: Telegram Message').item.json.message.chat.id }}",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6eb7505b-ccf5-4293-ae02-ad771773de1f",
      "name": "Post Suggestion Or Ask For Approval",
      "type": "n8n-nodes-base.telegram",
      "position": [
        3888,
        1296
      ],
      "parameters": {
        "text": "={{ $('AI: Draft & Revise Post').item.json.output }}",
        "chatId": "={{ $('Start: Telegram Message').first().json.message.chat.id }}",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "7df3aedd-07ad-48aa-a59b-d3ae8a66fb18",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2128,
        816
      ],
      "parameters": {
        "color": 7,
        "width": 1104,
        "height": 752,
        "content": "## 1. Capture Input\n\nReceives Telegram messages and converts voice notes to text using OpenAI Whisper"
      },
      "typeVersion": 1
    },
    {
      "id": "35d08d82-7491-439b-b4ba-cafeb6c7cc3b",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3248,
        816
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 752,
        "content": "## 2. AI Content Creation\n\nGenerates LinkedIn posts and handles revision requests through conversational memory"
      },
      "typeVersion": 1
    },
    {
      "id": "b9d9208f-728a-4c66-a1ad-70661a6724a4",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3616,
        816
      ],
      "parameters": {
        "color": 7,
        "width": 656,
        "height": 752,
        "content": "## 3. Approval & Publishing\n\nDetects approval keywords, extracts final content, and posts to LinkedIn via Blotato"
      },
      "typeVersion": 1
    },
    {
      "id": "5663605d-4dae-4208-bc9f-61b60d2242c8",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4288,
        816
      ],
      "parameters": {
        "color": 7,
        "width": 1168,
        "height": 752,
        "content": "## 4. Status Monitoring\n\nPolls Blotato API to verify publication and sends confirmation with post link to Telegram"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "66e55dce-2117-4fde-84cb-bcc137464fd8",
  "connections": {
    "Published?": {
      "main": [
        [
          {
            "node": "Send a confirmation message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "In Progress?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "In Progress?": {
      "main": [
        [
          {
            "node": "Give Blotat other 5s :)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send an error message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Input": {
      "main": [
        [
          {
            "node": "Check: ist it a Voice?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Voice File": {
      "main": [
        [
          {
            "node": "Speech to Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Speech to Text": {
      "main": [
        [
          {
            "node": "AI: Draft & Revise Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if Approved": {
      "main": [
        [
          {
            "node": "Approval: Extract Final Post Text",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post Suggestion Or Ask For Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check post status": {
      "main": [
        [
          {
            "node": "Published?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Give Blotat 5s :)": {
      "main": [
        [
          {
            "node": "Check post status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI: Draft & Revise Post",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Window Buffer Memory": {
      "ai_memory": [
        [
          {
            "node": "AI: Draft & Revise Post",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Check: ist it a Voice?": {
      "main": [
        [
          {
            "node": "Get Voice File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "AI: Draft & Revise Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI: Draft & Revise Post": {
      "main": [
        [
          {
            "node": "Check if Approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Give Blotat other 5s :)": {
      "main": [
        [
          {
            "node": "Check post status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start: Telegram Message": {
      "main": [
        [
          {
            "node": "Prepare Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create post with Blotato": {
      "main": [
        [
          {
            "node": "Give Blotat 5s :)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approval: Extract Final Post Text": {
      "main": [
        [
          {
            "node": "Create post with Blotato",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Suggestion Or Ask For Approval": {
      "main": [
        []
      ]
    }
  }
}