{
  "name": "Repurpose top X posts with Apify, OpenAI, Google Sheets and Slack",
  "nodes": [
    {
      "id": "0001",
      "name": "When Profile Is Submitted",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -1600,
        300
      ],
      "parameters": {
        "options": {
          "buttonLabel": "Start scrape"
        },
        "formTitle": "Scrape a Profile for Parasite Content",
        "formFields": {
          "values": [
            {
              "fieldType": "text",
              "fieldLabel": "Profile URL",
              "placeholder": "https://x.com/username",
              "requiredField": true
            },
            {
              "fieldType": "number",
              "fieldLabel": "Number of posts to pull",
              "placeholder": "50",
              "requiredField": false
            }
          ]
        },
        "formDescription": "Paste the X (Twitter) profile URL you want to mine for high-performing posts."
      },
      "typeVersion": 2.2
    },
    {
      "id": "0002",
      "name": "Scrape Posts With Apify",
      "type": "n8n-nodes-apify.apify",
      "onError": "continueErrorOutput",
      "position": [
        -1360,
        300
      ],
      "parameters": {
        "actorId": "={{ \"apidojo/tweet-scraper\" }}",
        "resource": "actor",
        "operation": "run",
        "runInputJson": "={\n  \"handles\": [\"{{ $json['Profile URL'].split('/').pop() }}\"],\n  \"maxTweets\": {{ $json['Number of posts to pull'] || 50 }},\n  \"sort\": \"Top\"\n}",
        "waitForFinish": true
      },
      "credentials": {
        "apifyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0010",
      "name": "Alert On Scrape Failure",
      "type": "n8n-nodes-base.slack",
      "position": [
        -1360,
        520
      ],
      "parameters": {
        "text": "=\u26a0\ufe0f Apify scrape failed for *{{ $json['Profile URL'] || 'unknown profile' }}*.\nError: {{ $json.error?.message || $json.message || 'see n8n execution log' }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "SLACK_CHANNEL_ID"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "0003",
      "name": "Fetch Scraped Posts From Apify",
      "type": "n8n-nodes-apify.apify",
      "position": [
        -1120,
        300
      ],
      "parameters": {
        "resource": "dataset",
        "datasetId": "={{ $json.defaultDatasetId }}",
        "operation": "getItems",
        "returnAll": true
      },
      "credentials": {
        "apifyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0004",
      "name": "Loop Over Scraped Posts",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -880,
        300
      ],
      "parameters": {
        "options": {},
        "batchSize": 1
      },
      "typeVersion": 3
    },
    {
      "id": "0016",
      "name": "Notify Batch Complete",
      "type": "n8n-nodes-base.slack",
      "position": [
        -880,
        520
      ],
      "parameters": {
        "text": "=\u2705 Scrape batch complete for this run. Check the Drafts sheet for new posts pending review.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "SLACK_CHANNEL_ID"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "0005",
      "name": "Check Post Relevance",
      "type": "n8n-nodes-base.openAi",
      "position": [
        -620,
        160
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {
          "temperature": 0.2,
          "responseFormat": "json_object"
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You judge whether a scraped post is a good candidate to repurpose for a personal brand about AI automation for agencies. Mark relevant=true only if the post has strong engagement signals and a hook/structure worth modeling."
            },
            {
              "role": "user",
              "content": "=Post text:\n{{ $json.text }}\n\nLikes: {{ $json.likeCount }}\nReplies: {{ $json.replyCount }}\nReposts: {{ $json.retweetCount }}"
            }
          ]
        },
        "resource": "text",
        "operation": "message"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "0006",
      "name": "Filter Relevant Posts",
      "type": "n8n-nodes-base.filter",
      "position": [
        -380,
        300
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ JSON.parse($json.message.content).relevant }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "0013",
      "name": "Check For Duplicate Post",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -140,
        300
      ],
      "parameters": {
        "options": {
          "alwaysOutputData": true
        },
        "resource": "sheet",
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $('Loop Over Scraped Posts').item.json.url }}",
              "lookupColumn": "source_url"
            }
          ]
        },
        "operation": "get",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Drafts"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "GOOGLE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "0014",
      "name": "If Post Is New",
      "type": "n8n-nodes-base.if",
      "position": [
        100,
        300
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "string",
                "operation": "empty"
              },
              "leftValue": "={{ $json.source_url }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "0007",
      "name": "Draft Repurposed Post",
      "type": "n8n-nodes-base.openAi",
      "position": [
        340,
        160
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1"
        },
        "options": {
          "temperature": 0.7,
          "responseFormat": "json_object"
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You rewrite high-performing posts into an original draft in Tamim's voice: a trust-driven, no-hype AI automation developer who runs an agency and builds in n8n/Python. Keep the hook structure and pacing of the source post but write fully original content, no plagiarism."
            },
            {
              "role": "user",
              "content": "=Original post to model (do not copy, restructure only):\n{{ $('Loop Over Scraped Posts').item.json.text }}"
            }
          ]
        },
        "resource": "text",
        "operation": "message"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "0008",
      "name": "Build Draft Row For Sheet",
      "type": "n8n-nodes-base.set",
      "position": [
        580,
        300
      ],
      "parameters": {
        "mode": "manual",
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "f1",
              "name": "source_url",
              "type": "string",
              "value": "={{ $('Loop Over Scraped Posts').item.json.url }}"
            },
            {
              "id": "f2",
              "name": "source_likes",
              "type": "number",
              "value": "={{ $('Loop Over Scraped Posts').item.json.likeCount }}"
            },
            {
              "id": "f3",
              "name": "draft",
              "type": "string",
              "value": "={{ JSON.parse($json.message.content).draft }}"
            },
            {
              "id": "f4",
              "name": "status",
              "type": "string",
              "value": "Needs review"
            },
            {
              "id": "f5",
              "name": "created_at",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        },
        "duplicateItem": false
      },
      "typeVersion": 3.4
    },
    {
      "id": "0009",
      "name": "Append Draft To Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        820,
        300
      ],
      "parameters": {
        "columns": {
          "value": {
            "draft": "={{ $json.draft }}",
            "status": "={{ $json.status }}",
            "created_at": "={{ $json.created_at }}",
            "source_url": "={{ $json.source_url }}",
            "source_likes": "={{ $json.source_likes }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Drafts"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "GOOGLE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "0017",
      "name": "Send Draft To Slack For Review",
      "type": "n8n-nodes-base.slack",
      "position": [
        1060,
        300
      ],
      "parameters": {
        "text": "=\ud83d\udcdd New draft ready for review\nSource: {{ $json.source_url }}\n\n{{ $json.draft }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "SLACK_CHANNEL_ID"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "sticky-0001",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2208,
        20
      ],
      "parameters": {
        "width": 560,
        "height": 900,
        "content": "## Repurpose top X posts with Apify, OpenAI, Google Sheets and Slack\n\n### How it works\n1. A form collects the X profile URL you want to mine for high-performing posts.\n2. An Apify actor scrapes the profile's recent posts; failures are caught and sent to Slack instead of breaking silently.\n3. Each post loops through one at a time and is scored for relevance with a structured AI response.\n4. Irrelevant posts are skipped; relevant ones are checked against the sheet so duplicates are never redrafted.\n5. New, relevant posts are rewritten into an original draft in your own voice.\n6. The draft is appended to Google Sheets and posted to Slack for human review before anything goes out.\n\n### Setup steps\n- [ ] Connect your Apify account and set the actor ID for the scraper you use.\n- [ ] Connect your OpenAI account (used for relevance scoring and drafting).\n- [ ] Connect Google Sheets and point it at your own spreadsheet/tab with columns: source_url, source_likes, draft, status, created_at.\n- [ ] Connect Slack and set the channel ID for failure alerts and reviewer notifications.\n\n### Customization\nSwap the Apify actor for a scraper of a different platform, or edit the AI prompts to match a different brand voice."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-0002",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1648,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 672,
        "height": 512,
        "content": "## Scrape source posts\n\nCollects a profile URL, runs the Apify actor, and pulls the resulting dataset. Scrape failures alert Slack instead of failing silently."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-0003",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -928,
        20
      ],
      "parameters": {
        "color": 7,
        "width": 688,
        "height": 672,
        "content": "## Loop and score relevance\n\nProcesses one post at a time and uses AI to judge whether it's worth repurposing before continuing."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-0004",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -188,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 304,
        "content": "## Skip duplicates\n\nChecks the sheet for the same source URL so a post never gets redrafted twice."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-0005",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        292,
        20
      ],
      "parameters": {
        "color": 7,
        "width": 912,
        "height": 448,
        "content": "## Draft, save and hand off for review\n\nRewrites the surviving post in your voice, saves it to Sheets, and notifies you on Slack so nothing publishes automatically."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "If Post Is New": {
      "main": [
        [
          {
            "node": "Draft Repurposed Post",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Loop Over Scraped Posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Post Relevance": {
      "main": [
        [
          {
            "node": "Filter Relevant Posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft Repurposed Post": {
      "main": [
        [
          {
            "node": "Build Draft Row For Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Relevant Posts": {
      "main": [
        [
          {
            "node": "Check For Duplicate Post",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Loop Over Scraped Posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append Draft To Sheets": {
      "main": [
        [
          {
            "node": "Send Draft To Slack For Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Scraped Posts": {
      "main": [
        [
          {
            "node": "Notify Batch Complete",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Check Post Relevance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Posts With Apify": {
      "main": [
        [
          {
            "node": "Fetch Scraped Posts From Apify",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Alert On Scrape Failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check For Duplicate Post": {
      "main": [
        [
          {
            "node": "If Post Is New",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Draft Row For Sheet": {
      "main": [
        [
          {
            "node": "Append Draft To Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Profile Is Submitted": {
      "main": [
        [
          {
            "node": "Scrape Posts With Apify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Scraped Posts From Apify": {
      "main": [
        [
          {
            "node": "Loop Over Scraped Posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Draft To Slack For Review": {
      "main": [
        [
          {
            "node": "Loop Over Scraped Posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}