{
  "name": "YouTube Processor - Full Version",
  "nodes": [
    {
      "parameters": {
        "content": "## YouTube Video Processor\n\n\u529f\u80fd\uff1a\n1. \u9a8c\u8bc1YouTube URL\n2. \u63d0\u53d6\u89c6\u9891ID\n3. \u83b7\u53d6\u89c6\u9891\u5143\u6570\u636e\uff08\u6a21\u62df\uff09\n4. \u751f\u6210\u6587\u7ae0\u5185\u5bb9\n5. \u8fd4\u56de\u5904\u7406\u7ed3\u679c",
        "height": 150,
        "width": 400,
        "color": "#FF6B6B"
      },
      "id": "note_doc",
      "name": "Documentation",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        250,
        50
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "youtube-processor-full",
        "responseMode": "lastNode",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              },
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              }
            ]
          }
        }
      },
      "id": "webhook_trigger",
      "name": "\u63a5\u6536YouTube URL",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [
        250,
        250
      ]
    },
    {
      "parameters": {
        "jsCode": "// \u9a8c\u8bc1URL\u5e76\u63d0\u53d6YouTube\u89c6\u9891ID\nconst input = $input.item.json.body || $input.item.json;\nconst url = input.youtube_url || input.url || '';\n\nif (!url) {\n  return {\n    success: false,\n    error: {\n      code: 'MISSING_URL',\n      message: 'YouTube URL is required',\n      timestamp: new Date().toISOString()\n    }\n  };\n}\n\n// \u652f\u6301\u591a\u79cdYouTube URL\u683c\u5f0f\nconst patterns = [\n  /(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/|youtube\\.com\\/shorts\\/)([\\w-]{11})/,\n  /youtube\\.com\\/embed\\/([\\w-]{11})/,\n  /youtube\\.com\\/v\\/([\\w-]{11})/\n];\n\nlet videoId = null;\nlet urlType = 'unknown';\n\nfor (const pattern of patterns) {\n  const match = url.match(pattern);\n  if (match) {\n    videoId = match[1];\n    if (pattern.source.includes('watch\\?v')) urlType = 'standard';\n    else if (pattern.source.includes('youtu\\.be')) urlType = 'short';\n    else if (pattern.source.includes('shorts')) urlType = 'shorts';\n    else if (pattern.source.includes('embed')) urlType = 'embed';\n    break;\n  }\n}\n\nif (!videoId) {\n  return {\n    success: false,\n    error: {\n      code: 'INVALID_URL',\n      message: 'Invalid YouTube URL format',\n      supportedFormats: [\n        'https://www.youtube.com/watch?v=VIDEO_ID',\n        'https://youtu.be/VIDEO_ID',\n        'https://youtube.com/shorts/VIDEO_ID',\n        'https://www.youtube.com/embed/VIDEO_ID'\n      ],\n      timestamp: new Date().toISOString()\n    }\n  };\n}\n\n// \u9a8c\u8bc1videoId\u683c\u5f0f\uff0811\u4e2a\u5b57\u7b26\u7684\u5b57\u6bcd\u6570\u5b57\u7ec4\u5408\uff09\nif (!/^[\\w-]{11}$/.test(videoId)) {\n  return {\n    success: false,\n    error: {\n      code: 'INVALID_VIDEO_ID',\n      message: 'Invalid YouTube video ID',\n      videoId: videoId,\n      timestamp: new Date().toISOString()\n    }\n  };\n}\n\nreturn {\n  success: true,\n  videoId: videoId,\n  originalUrl: url,\n  urlType: urlType,\n  language: input.language || 'zh-CN',\n  requestId: Math.random().toString(36).substr(2, 9),\n  processStep: 'url_validated',\n  timestamp: new Date().toISOString()\n};"
      },
      "id": "validate_url",
      "name": "\u9a8c\u8bc1URL\u5e76\u63d0\u53d6ID",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        450,
        250
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.success }}",
              "value2": true
            }
          ]
        }
      },
      "id": "check_validation",
      "name": "\u68c0\u67e5\u9a8c\u8bc1\u7ed3\u679c",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        650,
        250
      ]
    },
    {
      "parameters": {
        "jsCode": "// \u6a21\u62df\u83b7\u53d6YouTube\u89c6\u9891\u5143\u6570\u636e\n// \u5728\u5b9e\u9645\u5e94\u7528\u4e2d\uff0c\u8fd9\u91cc\u4f1a\u8c03\u7528YouTube Data API v3\nconst data = $input.item.json;\n\n// \u6a21\u62df\u89c6\u9891\u5143\u6570\u636e\uff08\u57fa\u4e8evideoId\u751f\u6210\u786e\u5b9a\u6027\u6570\u636e\uff09\nconst videoId = data.videoId;\nconst hash = videoId.split('').reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) & 0xffffffff, 0);\n\n// \u4f7f\u7528videoID\u751f\u6210\u4f2a\u968f\u673a\u7684\u89c6\u9891\u4fe1\u606f\nconst mockVideos = {\n  '_ss8tOxWAuo': {\n    title: '2024\u5e74AI\u6280\u672f\u53d1\u5c55\u603b\u7ed3',\n    description: '\u672c\u89c6\u9891\u5168\u9762\u56de\u987e\u4e862024\u5e74\u4eba\u5de5\u667a\u80fd\u9886\u57df\u7684\u91cd\u8981\u7a81\u7834\uff0c\u5305\u62ec\u5927\u6a21\u578b\u7684\u53d1\u5c55\u3001AI\u5728\u5404\u884c\u4e1a\u7684\u5e94\u7528\u3001\u4ee5\u53ca\u5bf9\u672a\u6765\u7684\u5c55\u671b\u3002',\n    channelTitle: '\u79d1\u6280\u524d\u6cbf',\n    duration: '15:30',\n    viewCount: Math.floor((hash % 1000000) + 50000),\n    likeCount: Math.floor((hash % 50000) + 1000),\n    publishDate: '2024-01-15T10:00:00Z',\n    tags: ['AI', '\u4eba\u5de5\u667a\u80fd', '\u79d1\u6280', '2024\u603b\u7ed3', '\u6280\u672f\u8d8b\u52bf']\n  }\n};\n\n// \u83b7\u53d6\u6216\u751f\u6210\u89c6\u9891\u4fe1\u606f\nlet videoInfo = mockVideos[videoId] || {\n  title: `\u89c6\u9891\u6807\u9898 - ${videoId}`,\n  description: `\u8fd9\u662f\u89c6\u9891ID\u4e3a${videoId}\u7684\u63cf\u8ff0\u5185\u5bb9\u3002\u89c6\u9891\u8bb2\u8ff0\u4e86\u5173\u4e8e\u6280\u672f\u548c\u521b\u65b0\u7684\u91cd\u8981\u8bdd\u9898\u3002`,\n  channelTitle: '\u79d1\u6280\u9891\u9053',\n  duration: `${Math.floor(Math.abs(hash % 30) + 1)}:${String(Math.abs(hash % 60)).padStart(2, '0')}`,\n  viewCount: Math.floor(Math.abs(hash % 1000000) + 10000),\n  likeCount: Math.floor(Math.abs(hash % 50000) + 500),\n  publishDate: new Date(Date.now() - Math.abs(hash % 365) * 24 * 60 * 60 * 1000).toISOString(),\n  tags: ['\u6280\u672f', '\u521b\u65b0', '\u89c6\u9891', '\u5185\u5bb9']\n};\n\n// \u6784\u5efa\u5b8c\u6574\u7684\u89c6\u9891\u5143\u6570\u636e\nconst videoMetadata = {\n  videoId: videoId,\n  title: videoInfo.title,\n  description: videoInfo.description,\n  channelTitle: videoInfo.channelTitle,\n  duration: videoInfo.duration,\n  durationSeconds: parseDuration(videoInfo.duration),\n  viewCount: videoInfo.viewCount,\n  likeCount: videoInfo.likeCount,\n  publishDate: videoInfo.publishDate,\n  publishDateFormatted: formatDate(videoInfo.publishDate),\n  tags: videoInfo.tags,\n  thumbnails: {\n    default: `https://img.youtube.com/vi/${videoId}/default.jpg`,\n    medium: `https://img.youtube.com/vi/${videoId}/mqdefault.jpg`,\n    high: `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`,\n    maxres: `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`\n  },\n  urls: {\n    watch: `https://www.youtube.com/watch?v=${videoId}`,\n    embed: `https://www.youtube.com/embed/${videoId}`,\n    share: `https://youtu.be/${videoId}`\n  }\n};\n\n// \u8f85\u52a9\u51fd\u6570\nfunction parseDuration(duration) {\n  const parts = duration.split(':');\n  return parseInt(parts[0]) * 60 + parseInt(parts[1]);\n}\n\nfunction formatDate(dateStr) {\n  const date = new Date(dateStr);\n  return date.toLocaleDateString('zh-CN', { \n    year: 'numeric',\n    month: 'long',\n    day: 'numeric'\n  });\n}\n\nreturn {\n  ...data,\n  videoMetadata: videoMetadata,\n  processStep: 'metadata_fetched',\n  processTime: Date.now() - new Date(data.timestamp).getTime()\n};"
      },
      "id": "fetch_metadata",
      "name": "\u83b7\u53d6\u89c6\u9891\u5143\u6570\u636e",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        850,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "// \u751f\u6210\u57fa\u4e8e\u89c6\u9891\u7684\u6587\u7ae0\u5185\u5bb9\nconst data = $input.item.json;\nconst metadata = data.videoMetadata;\nconst language = data.language || 'zh-CN';\n\n// \u751f\u6210\u6587\u7ae0\u5185\u5bb9\nconst article = {\n  title: metadata.title + ' - \u6df1\u5ea6\u89e3\u6790',\n  summary: `\u57fa\u4e8eYouTube\u89c6\u9891\u300a${metadata.title}\u300b\u751f\u6210\u7684\u5185\u5bb9\u89e3\u6790\u3002\u672c\u6587\u4ece\u89c6\u9891\u7684\u6838\u5fc3\u89c2\u70b9\u51fa\u53d1\uff0c\u6df1\u5165\u63a2\u8ba8\u76f8\u5173\u4e3b\u9898\uff0c\u4e3a\u8bfb\u8005\u63d0\u4f9b\u8be6\u7ec6\u7684\u77e5\u8bc6\u603b\u7ed3\u548c\u89c1\u89e3\u3002`,\n  \n  sections: [\n    {\n      heading: '\u89c6\u9891\u6982\u8ff0',\n      content: `\u672c\u6587\u57fa\u4e8e${metadata.channelTitle}\u9891\u9053\u53d1\u5e03\u7684\u89c6\u9891\u300a${metadata.title}\u300b\u3002\u8be5\u89c6\u9891\u65f6\u957f${metadata.duration}\uff0c\u5df2\u83b7\u5f97${metadata.viewCount.toLocaleString()}\u6b21\u89c2\u770b\uff0c\u53d1\u5e03\u4e8e${metadata.publishDateFormatted}\u3002\u89c6\u9891\u4e3b\u8981\u5185\u5bb9\u6db5\u76d6\u4e86${metadata.tags.slice(0, 3).join('\u3001')}\u7b49\u5173\u952e\u4e3b\u9898\u3002`,\n      type: 'introduction'\n    },\n    \n    {\n      heading: '\u6838\u5fc3\u5185\u5bb9\u89e3\u6790',\n      content: metadata.description,\n      type: 'main_content',\n      bulletPoints: [\n        '\u8981\u70b9\u4e00\uff1a\u89c6\u9891\u5f3a\u8c03\u4e86\u6280\u672f\u521b\u65b0\u7684\u91cd\u8981\u6027',\n        '\u8981\u70b9\u4e8c\uff1a\u63d0\u4f9b\u4e86\u5b9e\u7528\u7684\u89e3\u51b3\u65b9\u6848\u548c\u6848\u4f8b',\n        '\u8981\u70b9\u4e09\uff1a\u5c55\u671b\u4e86\u672a\u6765\u7684\u53d1\u5c55\u8d8b\u52bf',\n        '\u8981\u70b9\u56db\uff1a\u5206\u4eab\u4e86\u5b9d\u8d35\u7684\u7ecf\u9a8c\u548c\u89c1\u89e3'\n      ]\n    },\n    \n    {\n      heading: '\u5173\u952e\u6570\u636e\u4e0e\u7edf\u8ba1',\n      content: {\n        videoId: metadata.videoId,\n        duration: metadata.duration,\n        views: metadata.viewCount.toLocaleString() + '\u6b21\u89c2\u770b',\n        likes: metadata.likeCount.toLocaleString() + '\u4e2a\u70b9\u8d5e',\n        publishDate: metadata.publishDateFormatted,\n        channel: metadata.channelTitle,\n        tags: metadata.tags.join(', ')\n      },\n      type: 'statistics'\n    },\n    \n    {\n      heading: '\u5ef6\u4f38\u601d\u8003',\n      content: `\u4ece\u89c6\u9891\u5185\u5bb9\u53ef\u4ee5\u770b\u51fa\uff0c${metadata.tags[0]}\u9886\u57df\u6b63\u5728\u5feb\u901f\u53d1\u5c55\u3002\u5bf9\u4e8e\u5173\u6ce8\u8fd9\u4e00\u9886\u57df\u7684\u89c2\u4f17\u6765\u8bf4\uff0c\u7406\u89e3\u89c6\u9891\u4e2d\u7684\u6838\u5fc3\u89c2\u70b9\u5bf9\u4e8e\u628a\u63e1\u884c\u4e1a\u52a8\u6001\u81f3\u5173\u91cd\u8981\u3002\u5efa\u8bae\u89c2\u4f17\u7ed3\u5408\u81ea\u8eab\u9700\u6c42\uff0c\u6df1\u5165\u601d\u8003\u5982\u4f55\u5c06\u8fd9\u4e9b\u77e5\u8bc6\u5e94\u7528\u5230\u5b9e\u9645\u573a\u666f\u4e2d\u3002`,\n      type: 'reflection'\n    },\n    \n    {\n      heading: '\u76f8\u5173\u8d44\u6e90',\n      content: {\n        videoLink: metadata.urls.watch,\n        embedLink: metadata.urls.embed,\n        thumbnails: metadata.thumbnails,\n        suggestedTopics: metadata.tags.map(tag => `\u5173\u4e8e${tag}\u7684\u66f4\u591a\u8d44\u6e90`)\n      },\n      type: 'resources'\n    }\n  ],\n  \n  metadata: {\n    generatedAt: new Date().toISOString(),\n    language: language,\n    wordCount: Math.floor(Math.random() * 500 + 800),\n    estimatedReadTime: Math.floor(Math.random() * 3 + 3) + '\u5206\u949f',\n    source: 'YouTube Video',\n    sourceUrl: metadata.urls.watch,\n    videoId: metadata.videoId\n  }\n};\n\n// \u6dfb\u52a0\u793e\u4ea4\u5a92\u4f53\u6458\u8981\nconst socialSummary = {\n  twitter: `${article.title.substring(0, 100)}... #AI #\u6280\u672f\u5206\u4eab`,\n  linkedin: `\u6df1\u5165\u89e3\u6790${metadata.title}\uff0c\u63a2\u8ba8${metadata.tags[0]}\u9886\u57df\u7684\u53d1\u5c55\u8d8b\u52bf\u3002`,\n  weibo: `\u3010\u89c6\u9891\u89e3\u6790\u3011${article.title} \u57fa\u4e8eYouTube\u89c6\u9891\u7684\u6df1\u5ea6\u5185\u5bb9\u89e3\u6790\uff0c${metadata.viewCount}\u6b21\u89c2\u770b\u7684\u70ed\u95e8\u5185\u5bb9\uff01`\n};\n\nreturn {\n  ...data,\n  article: article,\n  socialSummary: socialSummary,\n  processStep: 'content_generated',\n  totalProcessTime: Date.now() - new Date(data.timestamp).getTime()\n};"
      },
      "id": "generate_content",
      "name": "\u751f\u6210\u6587\u7ae0\u5185\u5bb9",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1050,
        200
      ]
    },
    {
      "parameters": {
        "values": {
          "string": [
            {
              "name": "status",
              "value": "completed"
            },
            {
              "name": "message",
              "value": "YouTube\u89c6\u9891\u5904\u7406\u5b8c\u6210"
            },
            {
              "name": "success",
              "value": true
            }
          ],
          "number": [
            {
              "name": "finalProcessTime",
              "value": "={{ $json.totalProcessTime }}"
            }
          ]
        },
        "options": {}
      },
      "id": "add_final_metadata",
      "name": "\u6dfb\u52a0\u6700\u7ec8\u5143\u6570\u636e",
      "type": "n8n-nodes-base.set",
      "typeVersion": 2,
      "position": [
        1250,
        200
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json, null, 2) }}",
        "options": {}
      },
      "id": "return_success",
      "name": "\u8fd4\u56de\u6210\u529f\u7ed3\u679c",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        1450,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "// \u683c\u5f0f\u5316\u9519\u8bef\u54cd\u5e94\nconst error = $input.item.json;\n\nreturn {\n  success: false,\n  error: error.error || {\n    code: 'UNKNOWN_ERROR',\n    message: '\u5904\u7406\u8fc7\u7a0b\u4e2d\u53d1\u751f\u672a\u77e5\u9519\u8bef'\n  },\n  requestId: error.requestId || 'unknown',\n  timestamp: new Date().toISOString(),\n  processStep: 'error_occurred'\n};"
      },
      "id": "format_error",
      "name": "\u683c\u5f0f\u5316\u9519\u8bef",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        850,
        400
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "id": "return_error",
      "name": "\u8fd4\u56de\u9519\u8bef",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        1050,
        400
      ]
    }
  ],
  "connections": {
    "webhook_trigger": {
      "main": [
        [
          {
            "node": "validate_url",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "validate_url": {
      "main": [
        [
          {
            "node": "check_validation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "check_validation": {
      "main": [
        [
          {
            "node": "fetch_metadata",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "format_error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "fetch_metadata": {
      "main": [
        [
          {
            "node": "generate_content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "generate_content": {
      "main": [
        [
          {
            "node": "add_final_metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "add_final_metadata": {
      "main": [
        [
          {
            "node": "return_success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "format_error": {
      "main": [
        [
          {
            "node": "return_error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}