{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "5b076752-58e8-4bc4-b503-c0755685c2c1",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1552,
        -32
      ],
      "parameters": {
        "color": 5,
        "width": 340,
        "height": 1028,
        "content": "## \ud83c\udfa8 AI Content Repurposing Engine\n\nTransform any content into multiple formats for social media, email, and more.\n\n---\n\n### What It Does\n1. **Input:** Blog URL, YouTube URL, or raw text\n2. **Extract:** Scrapes article or fetches video transcript\n3. **Generate:** AI creates 7 content variations\n4. **Output:** Formatted Google Doc with everything\n\n---\n\n### Content Generated\n- 3x LinkedIn posts (hook + body + CTA)\n- Twitter/X thread (7 tweets)\n- Email newsletter (3 subject lines + body)\n- 5 Key takeaways\n- 5 Quote cards for graphics\n- 60-second video script\n\n---\n\n### Required Credentials\n- **OpenAI** - GPT-4o for content generation\n- **Google Docs** - Output destination\n\n### Optional Credentials\n- **YouTube API** - Better video metadata\n\n---\n\n### Setup\n1. Add OpenAI credential\n2. Add Google Docs credential\n3. (Optional) Add YouTube API key\n4. Activate workflow\n5. Fill out form & get your content!\n\n---\n\n### Tips\n- Longer content = better output\n- Blog posts typically work best\n- YouTube videos need captions enabled\n- Add target audience for better results\n\n---\n\n**Created by [Agentical AI](https://agenticalai.com)**\n\n\ud83d\udca1 Need custom automation? Visit us!"
      },
      "typeVersion": 1
    },
    {
      "id": "38d24169-79d5-4270-9878-c30a7be937d5",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1168,
        16
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 248,
        "content": "### 1\ufe0f\u20e3 Content Input\n\nForm accepts 3 input types:\n\n**Blog URL** \u2192 Fetched & scraped\n**YouTube URL** \u2192 Transcript extracted\n**Raw Text** \u2192 Direct input\n\nOptional fields:\n- Target audience\n- Custom instructions"
      },
      "typeVersion": 1
    },
    {
      "id": "195feb22-33c6-4091-b99c-bac66790f1a6",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        -48
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 220,
        "content": "### 2\ufe0f\u20e3 Content Extraction\n\n**Blog Path:**\nFetch HTML \u2192 Extract article content \u2192 Clean text\n\n**YouTube Path:**\nExtract video ID \u2192 Get metadata via API \u2192 Fetch transcript \u2192 Combine\n\n**Raw Text:**\nPass through with metadata\n\nAll paths merge into unified format for AI."
      },
      "typeVersion": 1
    },
    {
      "id": "4b56b137-4620-423a-8be2-dc097d148398",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        192,
        -16
      ],
      "parameters": {
        "color": 4,
        "width": 300,
        "height": 260,
        "content": "### 3\ufe0f\u20e3 AI Content Generation\n\nGPT analyzes content and generates:\n\n\u2705 **LinkedIn** - 3 variations with hooks\n\u2705 **Twitter** - 7-tweet thread\n\u2705 **Email** - Subject lines + newsletter\n\u2705 **Takeaways** - 5 key points\n\u2705 **Quotes** - 5 shareable snippets\n\u2705 **Video Script** - 60-sec format\n\nAI adapts tone to match source content."
      },
      "typeVersion": 1
    },
    {
      "id": "54c17b02-444c-4525-b3bd-52d19d74e02d",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        608,
        16
      ],
      "parameters": {
        "color": 6,
        "width": 280,
        "height": 228,
        "content": "### 4\ufe0f\u20e3 Google Docs Output\n\nCreates formatted document:\n- Source info & summary\n- All generated content\n- Organized by platform\n- Ready to copy/paste\n\nNew doc available in drive."
      },
      "typeVersion": 1
    },
    {
      "id": "021ef226-80f6-4c55-873e-ab59ddbd97b7",
      "name": "Content Input Form",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -1120,
        304
      ],
      "parameters": {
        "path": "content-repurpose",
        "options": {},
        "formTitle": "\ud83c\udfa8 AI Content Repurposing Engine",
        "formFields": {
          "values": [
            {
              "fieldType": "dropdown",
              "fieldLabel": "Content Type",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Blog Post URL"
                  },
                  {
                    "option": "YouTube Video URL"
                  },
                  {
                    "option": "Raw Text / Paste Content"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldType": "textarea",
              "fieldLabel": "Content Input",
              "placeholder": "Paste your URL or content here...\n\nExamples:\n\u2022 https://yourblog.com/article-title\n\u2022 https://youtube.com/watch?v=VIDEO_ID\n\u2022 Or paste your text directly",
              "requiredField": true
            },
            {
              "fieldLabel": "Target Audience (Optional)",
              "placeholder": "e.g., SaaS founders, marketing managers, developers..."
            },
            {
              "fieldType": "textarea",
              "fieldLabel": "Additional Instructions (Optional)",
              "placeholder": "Any specific requirements?\n\ne.g., Focus on the ROI angle, keep it casual, emphasize the technical details..."
            }
          ]
        },
        "formDescription": "Transform your blog posts, YouTube videos, or any text into LinkedIn posts, Twitter threads, email newsletters, and more!\n\nPowered by GPT-4o \u2022 Output saved to Google Docs"
      },
      "typeVersion": 2.1
    },
    {
      "id": "bcb415c7-8f8f-42dc-9b89-09a27c4fab44",
      "name": "Is Blog URL?",
      "type": "n8n-nodes-base.if",
      "position": [
        -880,
        304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "blog-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json['Content Type'] }}",
              "rightValue": "Blog Post URL"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "8eec62ef-8fd0-422b-873a-694758599bd2",
      "name": "Is YouTube URL?",
      "type": "n8n-nodes-base.if",
      "position": [
        -880,
        544
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "youtube-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json['Content Type'] }}",
              "rightValue": "YouTube Video URL"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "035512ee-bffa-489b-b9ff-11349324cef6",
      "name": "Fetch Blog Page",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -496,
        224
      ],
      "parameters": {
        "url": "={{ $('Content Input Form').item.json['Content Input'].trim() }}",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "390eba10-9978-4983-8d06-1fdce3056bfd",
      "name": "Extract Blog Content",
      "type": "n8n-nodes-base.code",
      "position": [
        -240,
        224
      ],
      "parameters": {
        "jsCode": "// Extract main content from HTML\nconst input = $('Content Input Form').first().json;\nconst response = $input.first().json;\nconst html = response.data || response.body || (typeof response === 'string' ? response : '');\n\nlet title = '';\nlet content = '';\nlet description = '';\nlet author = '';\n\ntry {\n  // Extract title\n  const titleMatch = html.match(/<title[^>]*>([^<]+)<\\/title>/i);\n  const ogTitleMatch = html.match(/<meta[^>]*property=[\"']og:title[\"'][^>]*content=[\"']([^\"']+)[\"']/i);\n  title = ogTitleMatch ? ogTitleMatch[1].trim() : (titleMatch ? titleMatch[1].trim() : '');\n  \n  // Extract meta description\n  const descMatch = html.match(/<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n                    html.match(/<meta[^>]*property=[\"']og:description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n                    html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*name=[\"']description[\"']/i);\n  description = descMatch ? descMatch[1].trim() : '';\n  \n  // Extract author\n  const authorMatch = html.match(/<meta[^>]*name=[\"']author[\"'][^>]*content=[\"']([^\"']+)[\"']/i);\n  author = authorMatch ? authorMatch[1].trim() : '';\n  \n  // Remove non-content elements\n  let cleanHtml = html\n    .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n    .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, '')\n    .replace(/<nav[^>]*>[\\s\\S]*?<\\/nav>/gi, '')\n    .replace(/<header[^>]*>[\\s\\S]*?<\\/header>/gi, '')\n    .replace(/<footer[^>]*>[\\s\\S]*?<\\/footer>/gi, '')\n    .replace(/<aside[^>]*>[\\s\\S]*?<\\/aside>/gi, '')\n    .replace(/<noscript[^>]*>[\\s\\S]*?<\\/noscript>/gi, '')\n    .replace(/<!--[\\s\\S]*?-->/gi, '');\n  \n  // Try to find article content (priority order)\n  const articleMatch = cleanHtml.match(/<article[^>]*>([\\s\\S]*?)<\\/article>/i);\n  const mainMatch = cleanHtml.match(/<main[^>]*>([\\s\\S]*?)<\\/main>/i);\n  const entryMatch = cleanHtml.match(/<div[^>]*class=[\"'][^\"']*(?:entry-content|post-content|article-content|blog-content)[^\"']*[\"'][^>]*>([\\s\\S]*?)<\\/div>/i);\n  const contentMatch = cleanHtml.match(/<div[^>]*class=[\"'][^\"']*(?:content|post|article|entry)[^\"']*[\"'][^>]*>([\\s\\S]*?)<\\/div>/i);\n  \n  let rawContent = articleMatch ? articleMatch[1] : \n                   mainMatch ? mainMatch[1] : \n                   entryMatch ? entryMatch[1] :\n                   contentMatch ? contentMatch[1] : cleanHtml;\n  \n  // Strip remaining HTML tags and clean up\n  content = rawContent\n    .replace(/<h[1-6][^>]*>/gi, '\\n\\n## ')\n    .replace(/<\\/h[1-6]>/gi, '\\n\\n')\n    .replace(/<p[^>]*>/gi, '\\n')\n    .replace(/<\\/p>/gi, '\\n')\n    .replace(/<br[^>]*>/gi, '\\n')\n    .replace(/<li[^>]*>/gi, '\\n\u2022 ')\n    .replace(/<[^>]+>/g, ' ')\n    .replace(/&nbsp;/g, ' ')\n    .replace(/&amp;/g, '&')\n    .replace(/&lt;/g, '<')\n    .replace(/&gt;/g, '>')\n    .replace(/&quot;/g, '\"')\n    .replace(/&#39;/g, \"'\")\n    .replace(/&hellip;/g, '...')\n    .replace(/&mdash;/g, '\u2014')\n    .replace(/&ndash;/g, '\u2013')\n    .replace(/\\n\\s*\\n\\s*\\n/g, '\\n\\n')\n    .replace(/[ \\t]+/g, ' ')\n    .trim();\n  \n  // Limit content length\n  if (content.length > 12000) {\n    content = content.substring(0, 12000) + '\\n\\n[Content truncated...]';\n  }\n  \n  if (content.length < 100) {\n    throw new Error('Could not extract meaningful content from page');\n  }\n  \n} catch (error) {\n  content = 'Error extracting content: ' + error.message;\n}\n\nreturn {\n  contentType: 'blog',\n  sourceUrl: input['Content Input'].trim(),\n  title: title,\n  author: author,\n  description: description,\n  content: content,\n  targetAudience: input['Target Audience (Optional)'] || '',\n  additionalInstructions: input['Additional Instructions (Optional)'] || '',\n  wordCount: content.split(/\\s+/).length,\n  extractedAt: new Date().toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "7d037403-97cb-4300-8c19-fbf3ce6e0a78",
      "name": "Extract Video ID",
      "type": "n8n-nodes-base.code",
      "position": [
        -560,
        416
      ],
      "parameters": {
        "jsCode": "// Extract YouTube video ID from URL\nconst input = $('Content Input Form').first().json;\nconst url = input['Content Input'].trim();\n\nlet videoId = '';\n\n// Handle various YouTube URL formats\nconst patterns = [\n  /(?:youtube\\.com\\/watch\\?v=)([^&\\s]+)/,\n  /(?:youtu\\.be\\/)([^?\\s]+)/,\n  /(?:youtube\\.com\\/embed\\/)([^?\\s]+)/,\n  /(?:youtube\\.com\\/v\\/)([^?\\s]+)/,\n  /(?:youtube\\.com\\/shorts\\/)([^?\\s]+)/,\n  /(?:youtube\\.com\\/live\\/)([^?\\s]+)/\n];\n\nfor (const pattern of patterns) {\n  const match = url.match(pattern);\n  if (match) {\n    videoId = match[1].split('&')[0]; // Remove any trailing params\n    break;\n  }\n}\n\nif (!videoId) {\n  throw new Error('Could not extract YouTube video ID from URL: ' + url);\n}\n\nreturn {\n  videoId: videoId,\n  sourceUrl: url,\n  targetAudience: input['Target Audience (Optional)'] || '',\n  additionalInstructions: input['Additional Instructions (Optional)'] || ''\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "1333f5d3-690a-48d7-bad4-ef34d92c204c",
      "name": "Get YouTube Metadata",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -352,
        416
      ],
      "parameters": {
        "url": "=https://www.googleapis.com/youtube/v3/videos",
        "options": {
          "timeout": 10000
        },
        "sendQuery": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "part",
              "value": "snippet,contentDetails,statistics"
            },
            {
              "name": "id",
              "value": "={{ $json.videoId }}"
            }
          ]
        },
        "nodeCredentialType": "youTubeOAuth2Api"
      },
      "credentials": {
        "httpQueryAuth": {
          "name": "<your credential>"
        },
        "youTubeOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "d2ca7488-fe40-46e7-98ee-c2c7154f43a2",
      "name": "Fetch YouTube Page",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -352,
        672
      ],
      "parameters": {
        "url": "=https://www.youtube.com/watch?v={{ $('Extract Video ID').item.json.videoId }}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "b80468b8-cf99-4797-91b0-ef3edadf5f31",
      "name": "Process YouTube Data",
      "type": "n8n-nodes-base.code",
      "position": [
        16,
        448
      ],
      "parameters": {
        "jsCode": "// Process YouTube data from metadata API and page scrape\nconst videoIdData = $('Extract Video ID').first().json;\nconst metadataResponse = $('Get YouTube Metadata').first().json;\nconst pageResponse = $('Fetch YouTube Page').first().json;\nconst pageHtml = pageResponse.data || pageResponse.body || '';\n\n// Extract video details from API\nconst video = metadataResponse.items?.[0]?.snippet || {};\nconst stats = metadataResponse.items?.[0]?.statistics || {};\nconst contentDetails = metadataResponse.items?.[0]?.contentDetails || {};\n\nconst title = video.title || 'Unknown Title';\nconst description = video.description || '';\nconst channelTitle = video.channelTitle || '';\nconst publishedAt = video.publishedAt || '';\nconst viewCount = stats.viewCount || '';\nconst likeCount = stats.likeCount || '';\n\n// Try to extract transcript/captions from page\nlet transcript = '';\n\ntry {\n  // Look for captions in the page data\n  const captionsMatch = pageHtml.match(/\"captions\":\\{\"playerCaptionsTracklistRenderer\":\\{\"captionTracks\":\\[([^\\]]+)\\]/);\n  \n  if (captionsMatch) {\n    // Found captions data - extract the URL\n    const captionData = captionsMatch[1];\n    const baseUrlMatch = captionData.match(/\"baseUrl\":\"([^\"]+)\"/);\n    \n    if (baseUrlMatch) {\n      // We found a caption URL - note this for the user\n      transcript = '[Captions available - using video metadata for content]';\n    }\n  }\n  \n  // Also try to find auto-generated transcript in ytInitialPlayerResponse\n  const playerMatch = pageHtml.match(/ytInitialPlayerResponse\\s*=\\s*({.+?});/);\n  if (playerMatch) {\n    try {\n      const playerData = JSON.parse(playerMatch[1]);\n      if (playerData.captions?.playerCaptionsTracklistRenderer?.captionTracks) {\n        transcript = '[Auto-captions detected]';\n      }\n    } catch (e) {\n      // JSON parse failed, continue\n    }\n  }\n} catch (e) {\n  // Transcript extraction failed, continue with metadata\n}\n\n// Build comprehensive content from available data\nlet content = `# ${title}\\n\\n`;\ncontent += `**Channel:** ${channelTitle}\\n`;\nif (publishedAt) {\n  content += `**Published:** ${new Date(publishedAt).toLocaleDateString()}\\n`;\n}\nif (viewCount) {\n  content += `**Views:** ${parseInt(viewCount).toLocaleString()}\\n`;\n}\ncontent += `\\n---\\n\\n`;\n\nif (description) {\n  content += `## Video Description\\n\\n${description}\\n\\n`;\n}\n\nif (transcript) {\n  content += `\\n---\\n\\n*Note: ${transcript}*\\n`;\n}\n\n// Truncate if too long\nif (content.length > 12000) {\n  content = content.substring(0, 12000) + '\\n\\n[Content truncated...]';\n}\n\nreturn {\n  contentType: 'youtube',\n  sourceUrl: videoIdData.sourceUrl,\n  videoId: videoIdData.videoId,\n  title: title,\n  channelTitle: channelTitle,\n  description: description.substring(0, 1000),\n  content: content,\n  viewCount: viewCount,\n  likeCount: likeCount,\n  publishedAt: publishedAt,\n  targetAudience: videoIdData.targetAudience || '',\n  additionalInstructions: videoIdData.additionalInstructions || '',\n  wordCount: content.split(/\\s+/).length,\n  extractedAt: new Date().toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "4e6a0467-8168-4afa-9bfb-8540e7fe627f",
      "name": "Process Raw Text",
      "type": "n8n-nodes-base.code",
      "position": [
        -384,
        896
      ],
      "parameters": {
        "jsCode": "// Pass raw text through with metadata\nconst input = $('Content Input Form').first().json;\nconst rawText = input['Content Input'].trim();\n\n// Try to extract a title from first line\nlet title = '';\nconst lines = rawText.split('\\n').filter(l => l.trim());\nconst firstLine = lines[0] || '';\n\n// Use first line as title if it's short enough and looks like a title\nif (firstLine.length < 150 && firstLine.length > 3) {\n  // Remove markdown heading markers if present\n  title = firstLine.replace(/^#+\\s*/, '').trim();\n}\n\nlet content = rawText;\n\n// Limit content length\nif (content.length > 12000) {\n  content = content.substring(0, 12000) + '\\n\\n[Content truncated...]';\n}\n\nreturn {\n  contentType: 'raw_text',\n  sourceUrl: '',\n  title: title || 'User Provided Content',\n  description: '',\n  content: content,\n  targetAudience: input['Target Audience (Optional)'] || '',\n  additionalInstructions: input['Additional Instructions (Optional)'] || '',\n  wordCount: content.split(/\\s+/).length,\n  extractedAt: new Date().toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "7fed92f5-abef-462c-84e0-c814c6d652ed",
      "name": "AI Content Generator",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        400,
        432
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5.1",
          "cachedResultName": "GPT-5.1"
        },
        "options": {
          "maxTokens": 4096,
          "temperature": 0.7
        },
        "messages": {
          "values": [
            {
              "content": "=You are an expert content strategist, copywriter, and social media specialist. Your task is to repurpose content into multiple platform-optimized formats.\n\n## Your Approach\n1. First, deeply analyze the source content to understand:\n   - Core message and key insights\n   - Tone and voice (professional, casual, technical, etc.)\n   - Target audience (if specified, otherwise infer)\n   - Unique angles and quotable moments\n\n2. Then create content that:\n   - Maintains the original value and insights\n   - Adapts to each platform's best practices\n   - Uses hooks and engagement techniques\n   - Provides clear calls-to-action\n\n## Output Requirements\nYou MUST respond with valid JSON in this exact structure:\n\n```json\n{\n  \"analysis\": {\n    \"contentSummary\": \"<2-3 sentence summary of the core message>\",\n    \"detectedTone\": \"<Professional|Casual|Educational|Inspirational|Technical|Conversational|Authoritative>\",\n    \"mainTopics\": [\"<topic1>\", \"<topic2>\", \"<topic3>\"],\n    \"targetAudience\": \"<identified or inferred target audience>\",\n    \"keyMessage\": \"<the single most important takeaway>\"\n  },\n  \n  \"linkedInPosts\": [\n    {\n      \"style\": \"Story-driven\",\n      \"hook\": \"<attention-grabbing first line - make them stop scrolling>\",\n      \"body\": \"<main content with strategic line breaks for readability, 150-200 words>\",\n      \"cta\": \"<call to action - question or engagement prompt>\",\n      \"hashtags\": [\"#relevant\", \"#hashtags\", \"#max5\"]\n    },\n    {\n      \"style\": \"List/Tips format\",\n      \"hook\": \"<hook>\",\n      \"body\": \"<body>\",\n      \"cta\": \"<cta>\",\n      \"hashtags\": []\n    },\n    {\n      \"style\": \"Contrarian/Hot take\",\n      \"hook\": \"<hook>\",\n      \"body\": \"<body>\",\n      \"cta\": \"<cta>\",\n      \"hashtags\": []\n    }\n  ],\n  \n  \"twitterThread\": {\n    \"tweets\": [\n      \"<Tweet 1: Hook - must grab attention, max 280 chars>\",\n      \"<Tweet 2: Context/Problem>\",\n      \"<Tweet 3: Key insight 1>\",\n      \"<Tweet 4: Key insight 2>\",\n      \"<Tweet 5: Key insight 3>\",\n      \"<Tweet 6: Summary/So what?>\",\n      \"<Tweet 7: CTA + link placeholder>\"\n    ],\n    \"altHook\": \"<alternative opening tweet for A/B testing>\"\n  },\n  \n  \"emailNewsletter\": {\n    \"subjectLines\": [\n      \"<option 1 - curiosity-driven>\",\n      \"<option 2 - benefit-focused>\",\n      \"<option 3 - direct/clear>\"\n    ],\n    \"previewText\": \"<50-100 char preview that complements subject>\",\n    \"greeting\": \"<personalized opening>\",\n    \"body\": \"<newsletter body: intro \u2192 main points \u2192 conclusion, conversational tone>\",\n    \"cta\": \"<clear call-to-action>\",\n    \"signOff\": \"<professional sign-off>\"\n  },\n  \n  \"keyTakeaways\": [\n    \"<actionable takeaway 1>\",\n    \"<actionable takeaway 2>\",\n    \"<actionable takeaway 3>\",\n    \"<actionable takeaway 4>\",\n    \"<actionable takeaway 5>\"\n  ],\n  \n  \"quoteCards\": [\n    {\n      \"quote\": \"<impactful, shareable quote - 10-20 words>\",\n      \"context\": \"<brief context for the quote>\"\n    },\n    { \"quote\": \"<quote 2>\", \"context\": \"<context>\" },\n    { \"quote\": \"<quote 3>\", \"context\": \"<context>\" },\n    { \"quote\": \"<quote 4>\", \"context\": \"<context>\" },\n    { \"quote\": \"<quote 5>\", \"context\": \"<context>\" }\n  ],\n  \n  \"shortFormVideoScript\": {\n    \"format\": \"Hook \u2192 Problem \u2192 Solution \u2192 Proof \u2192 CTA\",\n    \"targetLength\": \"60 seconds\",\n    \"sections\": {\n      \"hook\": \"<0-3 sec: pattern interrupt, make them stop scrolling>\",\n      \"problem\": \"<3-12 sec: identify pain point they relate to>\",\n      \"solution\": \"<12-35 sec: present the insight/solution>\",\n      \"proof\": \"<35-50 sec: evidence, example, or story>\",\n      \"cta\": \"<50-60 sec: what should they do next?>\"\n    },\n    \"visualNotes\": \"<suggestions for b-roll, text overlays, or transitions>\"\n  }\n}\n```\n\n## Platform Guidelines\n\n**LinkedIn:**\n- First line is EVERYTHING - make it scroll-stopping\n- Use line breaks for readability (no walls of text)\n- Personal stories and lessons outperform generic advice\n- End with engagement question or clear CTA\n- 5 hashtags max, mix of broad and niche\n\n**Twitter/X:**\n- Each tweet must work standalone AND as part of thread\n- Tweet 1 determines if anyone reads the rest\n- Use numbers, contrarian takes, or curiosity gaps\n- Keep most tweets under 200 chars for retweets\n- End with clear CTA\n\n**Email:**\n- Subject line is 80% of the battle\n- Preview text should complement, not repeat subject\n- Write like you're emailing a smart friend\n- One clear CTA per email\n\n**Quote Cards:**\n- Must work WITHOUT context\n- Emotional or thought-provoking\n- Easy to read at a glance\n- No jargon\n\n**Video Script:**\n- Hook in first 3 seconds or they scroll\n- Speak directly to viewer (\"you\")\n- One idea per video\n- Clear, actionable ending"
            },
            {
              "content": "=Repurpose this content into multiple platform-optimized formats:\n\n---\n\n**Source Type:** {{ $json.contentType }}\n**Title:** {{ $json.title }}\n{{ $json.sourceUrl ? '**Source URL:** ' + $json.sourceUrl : '' }}\n{{ $json.channelTitle ? '**Channel:** ' + $json.channelTitle : '' }}\n{{ $json.author ? '**Author:** ' + $json.author : '' }}\n**Word Count:** ~{{ $json.wordCount }} words\n\n{{ $json.targetAudience ? '**Target Audience:** ' + $json.targetAudience : '' }}\n{{ $json.additionalInstructions ? '**Special Instructions:** ' + $json.additionalInstructions : '' }}\n\n---\n\n## CONTENT TO REPURPOSE:\n\n{{ $json.content }}\n\n---\n\nGenerate all content variations. Respond with valid JSON only."
            }
          ]
        },
        "jsonOutput": true
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "a94b8439-4645-4270-851a-ccecabba2105",
      "name": "Format Document Content",
      "type": "n8n-nodes-base.code",
      "position": [
        880,
        432
      ],
      "parameters": {
        "jsCode": "// Parse AI response and build formatted Google Doc content\n\n// 1) Figure out where the normalized source content came from\nlet sourceData = null;\n\nfunction safeGetNodeFirstJson(nodeName) {\n  try {\n    return $(nodeName).first().json;\n  } catch (e) {\n    return null;\n  }\n}\n\n// Try blog path\nsourceData = safeGetNodeFirstJson('Extract Blog Content');\n\n// If not blog, try YouTube path\nif (!sourceData || !sourceData.contentType) {\n  sourceData = safeGetNodeFirstJson('Process YouTube Data');\n}\n\n// If not YouTube, try raw text path\nif (!sourceData || !sourceData.contentType) {\n  sourceData = safeGetNodeFirstJson('Process Raw Text');\n}\n\nif (!sourceData) {\n  throw new Error('Could not determine source content (blog / YouTube / raw text). Make sure one of the extraction nodes ran successfully.');\n}\n\n// 2) Get AI response from previous node\nconst aiResponse = $input.first().json;\n\n// 3) Parse AI JSON safely\nlet content;\ntry {\n  if (typeof aiResponse.message?.content === 'string') {\n    // Clean any markdown code blocks\n    let jsonStr = aiResponse.message.content\n      .replace(/```json\\n?/g, '')\n      .replace(/```\\n?/g, '')\n      .trim();\n    content = JSON.parse(jsonStr);\n  } else {\n    content = aiResponse.message?.content || aiResponse;\n  }\n} catch (e) {\n  throw new Error(\n    'Failed to parse AI response: ' +\n      e.message +\n      '\\n\\nRaw response: ' +\n      JSON.stringify(aiResponse).substring(0, 500)\n  );\n}\n\n// 4) Build Google Doc content\nconst cleanTitle = sourceData.title || 'Repurposed Content';\nconst docTitle =\n  `Repurposed: ${cleanTitle.substring(0, 60)}` +\n  (cleanTitle.length > 60 ? '...' : '');\n\nlet doc = '';\n\n// Header\ndoc += `# ${cleanTitle}\\n\\n`;\ndoc += `\ud83d\udcc5 Generated: ${new Date().toLocaleDateString('en-US', {\n  weekday: 'long',\n  year: 'numeric',\n  month: 'long',\n  day: 'numeric',\n})}\\n`;\n\ndoc += `\ud83d\udcdd Source: ${\n  sourceData.contentType === 'youtube'\n    ? 'YouTube Video'\n    : sourceData.contentType === 'blog'\n    ? 'Blog Post'\n    : 'Raw Text'\n}\\n`;\n\nif (sourceData.sourceUrl) {\n  doc += `\ud83d\udd17 URL: ${sourceData.sourceUrl}\\n`;\n}\ndoc += `\\n---\\n\\n`;\n\n// Analysis Summary\nif (content.analysis) {\n  doc += `## \ud83d\udcca Content Analysis\\n\\n`;\n  doc += `**Summary:** ${content.analysis.contentSummary || 'N/A'}\\n\\n`;\n  doc += `**Detected Tone:** ${content.analysis.detectedTone || 'N/A'}\\n`;\n  doc += `**Target Audience:** ${content.analysis.targetAudience || 'N/A'}\\n`;\n  doc += `**Key Message:** ${content.analysis.keyMessage || 'N/A'}\\n`;\n  doc += `**Main Topics:** ${(content.analysis.mainTopics || []).join(', ')}\\n\\n`;\n  doc += `---\\n\\n`;\n}\n\n// LinkedIn Posts\ndoc += `## \ud83d\udcbc LinkedIn Posts\\n\\n`;\nif (content.linkedInPosts && content.linkedInPosts.length > 0) {\n  content.linkedInPosts.forEach((post, i) => {\n    doc += `### Option ${i + 1}: ${post.style || 'Variation ' + (i + 1)}\\n\\n`;\n    doc += `**HOOK:**\\n${post.hook}\\n\\n`;\n    doc += `**BODY:**\\n${post.body}\\n\\n`;\n    doc += `**CTA:**\\n${post.cta}\\n\\n`;\n    if (post.hashtags && post.hashtags.length > 0) {\n      doc += `**Hashtags:** ${post.hashtags.join(' ')}\\n\\n`;\n    }\n    doc += `---\\n\\n`;\n  });\n} else {\n  doc += `*No LinkedIn posts generated*\\n\\n`;\n}\n\n// Twitter Thread\ndoc += `## \ud83d\udc26 Twitter/X Thread\\n\\n`;\nif (content.twitterThread?.tweets && content.twitterThread.tweets.length > 0) {\n  content.twitterThread.tweets.forEach((tweet, i) => {\n    doc += `**${i + 1}/${content.twitterThread.tweets.length}**\\n${tweet}\\n\\n`;\n  });\n  if (content.twitterThread.altHook) {\n    doc += `**\ud83d\udd04 Alternative Hook (for A/B testing):**\\n${content.twitterThread.altHook}\\n\\n`;\n  }\n} else {\n  doc += `*No Twitter thread generated*\\n\\n`;\n}\ndoc += `---\\n\\n`;\n\n// Email Newsletter\ndoc += `## \ud83d\udce7 Email Newsletter\\n\\n`;\nif (content.emailNewsletter) {\n  const email = content.emailNewsletter;\n  doc += `### Subject Line Options\\n`;\n  (email.subjectLines || []).forEach((subj, i) => {\n    doc += `${i + 1}. ${subj}\\n`;\n  });\n  doc += `\\n**Preview Text:** ${email.previewText || ''}\\n\\n`;\n  doc += `### Email Body\\n\\n`;\n  if (email.greeting) doc += `${email.greeting}\\n\\n`;\n  doc += `${email.body || ''}\\n\\n`;\n  if (email.cta) doc += `**CTA:** ${email.cta}\\n\\n`;\n  if (email.signOff) doc += `${email.signOff}\\n\\n`;\n} else {\n  doc += `*No email newsletter generated*\\n\\n`;\n}\ndoc += `---\\n\\n`;\n\n// Key Takeaways\ndoc += `## \ud83c\udfaf Key Takeaways\\n\\n`;\nif (content.keyTakeaways && content.keyTakeaways.length > 0) {\n  content.keyTakeaways.forEach((takeaway, i) => {\n    doc += `${i + 1}. ${takeaway}\\n`;\n  });\n} else {\n  doc += `*No takeaways generated*\\n`;\n}\ndoc += `\\n---\\n\\n`;\n\n// Quote Cards\ndoc += `## \ud83d\udcac Quote Cards\\n\\n`;\ndoc += `*Ready for social media graphics*\\n\\n`;\nif (content.quoteCards && content.quoteCards.length > 0) {\n  content.quoteCards.forEach((item) => {\n    const quote = typeof item === 'string' ? item : item.quote;\n    const context = typeof item === 'object' ? item.context : '';\n    doc += `> \"${quote}\"\\n`;\n    if (context) doc += `> *\u2014 ${context}*\\n`;\n    doc += `\\n`;\n  });\n} else {\n  doc += `*No quotes generated*\\n`;\n}\ndoc += `---\\n\\n`;\n\n// Video Script\ndoc += `## \ud83c\udfac Short-Form Video Script\\n\\n`;\nif (content.shortFormVideoScript) {\n  const script = content.shortFormVideoScript;\n  doc += `**Format:** ${script.format || 'Hook \u2192 Problem \u2192 Solution \u2192 CTA'}\\n`;\n  doc += `**Target Length:** ${script.targetLength || '60 seconds'}\\n\\n`;\n\n  if (script.sections) {\n    doc += `### \ud83c\udfa3 HOOK (0-3 sec)\\n${script.sections.hook || ''}\\n\\n`;\n    doc += `### \u2753 PROBLEM (3-12 sec)\\n${script.sections.problem || ''}\\n\\n`;\n    doc += `### \ud83d\udca1 SOLUTION (12-35 sec)\\n${script.sections.solution || ''}\\n\\n`;\n    doc += `### \u2705 PROOF (35-50 sec)\\n${script.sections.proof || ''}\\n\\n`;\n    doc += `### \ud83d\udc49 CTA (50-60 sec)\\n${script.sections.cta || ''}\\n\\n`;\n  }\n\n  if (script.visualNotes) {\n    doc += `### \ud83c\udfa5 Visual Notes\\n${script.visualNotes}\\n\\n`;\n  }\n} else {\n  doc += `*No video script generated*\\n`;\n}\n\ndoc += `---\\n\\n`;\ndoc += `*Generated by AI Content Repurposing Engine*\\n`;\ndoc += `*Created by [Agentical AI](https://agenticalai.com)*`;\n\n// 5) Return data for Google Docs node\nreturn {\n  docTitle: docTitle,\n  docContent: doc,\n  sourceTitle: sourceData.title,\n  sourceType: sourceData.contentType,\n  sourceUrl: sourceData.sourceUrl || null,\n  analysis: content.analysis || {},\n  generatedAt: new Date().toISOString(),\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "12f82b77-ccac-4d0b-8332-65f5f332652b",
      "name": "Create Google Doc",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        1072,
        432
      ],
      "parameters": {
        "title": "={{ $json.docTitle }}",
        "folderId": "default"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "72e308c8-ac7a-4a07-9319-8a5d035e1c5d",
      "name": "Write Content",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        1280,
        432
      ],
      "parameters": {
        "actionsUi": {
          "actionFields": [
            {
              "text": "={{ $('Format Document Content').item.json.docContent }}",
              "action": "insert"
            }
          ]
        },
        "operation": "update",
        "documentURL": "={{ $json.id }}"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "f46807fa-55c1-41eb-9524-6939d753d36c",
      "name": "Prepare Response",
      "type": "n8n-nodes-base.code",
      "position": [
        1488,
        432
      ],
      "parameters": {
        "jsCode": "// Prepare final response data\nconst docData = $('Write Content').first().json;\nconst prepData = $('Format Document Content').first().json;\n\nconst docUrl = `https://docs.google.com/document/d/${docData.documentId}/edit`;\n\nreturn {\n  success: true,\n  documentUrl: docUrl,\n  documentId: docData.documentId,\n  documentTitle: prepData.docTitle,\n  sourceType: prepData.sourceType,\n  sourceTitle: prepData.sourceTitle,\n  sourceUrl: prepData.sourceUrl,\n  analysis: prepData.analysis,\n  generatedAt: prepData.generatedAt\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "a129239a-e376-4e35-a036-850e6f8b6013",
      "name": "Edit Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        1696,
        432
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "00d35c7e-ac6d-43b4-abbb-66de177fc32a",
              "name": "documentUrl",
              "type": "string",
              "value": "={{ $json.documentUrl }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    }
  ],
  "connections": {
    "Is Blog URL?": {
      "main": [
        [
          {
            "node": "Fetch Blog Page",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Is YouTube URL?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write Content": {
      "main": [
        [
          {
            "node": "Prepare Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Blog Page": {
      "main": [
        [
          {
            "node": "Extract Blog Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is YouTube URL?": {
      "main": [
        [
          {
            "node": "Extract Video ID",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Process Raw Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Video ID": {
      "main": [
        [
          {
            "node": "Get YouTube Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Response": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Raw Text": {
      "main": [
        [
          {
            "node": "AI Content Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Google Doc": {
      "main": [
        [
          {
            "node": "Write Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Content Input Form": {
      "main": [
        [
          {
            "node": "Is Blog URL?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch YouTube Page": {
      "main": [
        [
          {
            "node": "Process YouTube Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Content Generator": {
      "main": [
        [
          {
            "node": "Format Document Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Blog Content": {
      "main": [
        [
          {
            "node": "AI Content Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get YouTube Metadata": {
      "main": [
        [
          {
            "node": "Fetch YouTube Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process YouTube Data": {
      "main": [
        [
          {
            "node": "AI Content Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Document Content": {
      "main": [
        [
          {
            "node": "Create Google Doc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}