X推文建议
高级
这是一个AI, Marketing领域的自动化工作流,包含 21 个节点。主要使用 Code, Html, SplitOut, Aggregate, FormTrigger 等节点,结合人工智能技术实现智能自动化。 根据您的个人资料查找相关X推文并提供回复建议
前置要求
- •可能需要目标 API 的认证凭证
- •OpenAI API Key
使用的节点 (21)
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "kMKlrWsoDftOxsqE",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "X 推文建议",
"tags": [],
"nodes": [
{
"id": "5fc895f6-b188-4900-81c7-b0eeebd1d571",
"name": "获取用户推文",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1220,
240
],
"parameters": {
"url": "https://api.twitterapi.io/twitter/user/last_tweets",
"options": {
"timeout": 30000
},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "userName",
"value": "={{ $json[\"What's your name on X (it's within your url)\"] }}"
},
{
"name": "count",
"value": "20"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"id": "ptKPKaEhgzwgwNXi",
"name": "Header Auth account"
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "299a1ada-646c-453c-85e5-c340e6df2dfc",
"name": "映射推文",
"type": "n8n-nodes-base.code",
"position": [
-1220,
440
],
"parameters": {
"jsCode": "\n return { tweets: $input.first().json.data.tweets };\n \n\n"
},
"typeVersion": 2
},
{
"id": "72b79ff2-055b-4842-8a60-7b8cef9dedd0",
"name": "表单提交时",
"type": "n8n-nodes-base.formTrigger",
"position": [
-1220,
0
],
"webhookId": "cf0416df-72fb-4660-9f6a-1b29d03a18ec",
"parameters": {
"options": {},
"formTitle": "Perfect Niche Tweets",
"formFields": {
"values": [
{
"fieldLabel": "What's your name on X (it's within your url)",
"placeholder": "OnePromptMagic"
},
{
"fieldType": "dropdown",
"fieldLabel": "What are your current goals on X",
"multiselect": true,
"fieldOptions": {
"values": [
{
"option": "Increase followers"
},
{
"option": "Boost impressions or reach"
},
{
"option": "Boost engagement"
},
{
"option": "Write a viral tweet"
},
{
"option": "Just be helpful and motivating"
}
]
}
},
{
"fieldLabel": "Additional Infos (Optional)",
"placeholder": "I'm recently deep into n8n flows, publishing one flow a week, incorporate that where it fits"
}
]
},
"formDescription": "Enter your details and goals, we will then analyse your profile, analyse trendings topics that might be a good fit for you and finally draft some perfect tweets for you"
},
"typeVersion": 2.2
},
{
"id": "ce05506b-fba3-40ff-a28f-24434175f6b7",
"name": "拆分输出",
"type": "n8n-nodes-base.splitOut",
"position": [
-320,
240
],
"parameters": {
"options": {},
"fieldToSplitOut": "queries"
},
"typeVersion": 1
},
{
"id": "445f99d3-95ec-4c94-95b7-c64bb0783da1",
"name": "聚合",
"type": "n8n-nodes-base.aggregate",
"position": [
120,
0
],
"parameters": {
"options": {},
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "tweets"
}
]
}
},
"typeVersion": 1
},
{
"id": "0511ce11-d316-4598-9e49-9a4df3e8dc95",
"name": "推文写手",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
60,
320
],
"parameters": {
"text": "=you are an expert on X conversions and brand building. the user has the following goals: {{ $('On form submission').item.json[\"What are your current goals on X\"] }}\nhere is the user's detailed user profile: {{ $('User Profile Analyser').item.json.output.toJsonString() }}\nhere is the user's bio: {{ $('Map Tweets').item.json.tweets[0].author.profile_bio.toJsonString()[\"description\"] }}\nHere are trending tweets that might be interesting for the user to interact with: \n{{ $json.tweets.toJsonString() }}\ncraft 10 tweets for the user, either interact with one of the listed tweets or craft an original tweet, based on what you think will bring the user closer to their goal. You can only write up to 2 original tweet, all others have to be responses. When responding, try to engage with the author, not self promote. Never write hashtags within the tweets. Use exclamation marks sparingly. Keep text short and authentic. some sarcasm can help, as long as it's not cringeworthy.\n\n🚫 Words to Avoid (AI Red Flags)\nNever overuse these AI-typical words:\nCrucial, delve, dive, tapestry\nFurthermore, consequently, moreover\n\"In today's [adjective] world of...\"\n\"Not only... but also\"\n\"By [doing X]...\" (especially in conclusions)\n✅ AI Writing Instructions\n1. Vary Sentence Structure\nMix short sentences (5-10 words) with longer ones (15-25 words)\nAvoid starting consecutive sentences with the same word patterns\nUse contractions naturally (don't, won't, it's, we're)\nBreak up long paragraphs with single-sentence paragraphs for emphasis\n2. Simulate Personal Voice\nUse first-person occasionally: \"I've noticed...\", \"In my view...\"\nInclude hypothetical scenarios: \"Imagine if...\", \"Picture this...\"\nReference common experiences: \"We've all been there when...\"\n3. Break Formulaic Patterns\nAvoid repetitive paragraph structures (intro-body-conclusion format)\nDon't restate the same concept multiple times in different words\nVary transition words instead of always using \"however,\" \"therefore,\" \"additionally\"\n4. Natural Language Patterns\nUse incomplete sentences for emphasis. Like this.\nInclude rhetorical questions to engage readers\nAdd mild interruptions with em-dashes--like this--for natural flow\nUse casual connectors: \"Plus,\" \"And here's the thing,\" \"Look,\"\n5. Content Structure Variation\nStart some paragraphs with questions\nUse specific numbers instead of vague terms (say \"73%\" not \"most\")\nInclude conversational asides in parentheses (because who doesn't love those?)\nVary list formats: bullets, numbers, and embedded lists within paragraphs\n6. Tone Authenticity\nMatch the complexity level to the audience consistently\nUse industry jargon appropriately but sparingly\nInclude mild opinions and stance-taking language\nWrite with confident assertions rather than hedging every statement\nKey instruction: Simulate the natural inconsistencies and stylistic quirks that make human writing distinctive.\n\ntweets to ignore: anything crypto related, politics\nif in the relevant tweets array there are several tweets of the same user, only respond to up to 1 one of those.\n\nHere is additional info by the user for this request:\n{{ $('On form submission').item.json[\"Additional Infos (Optional)\"] }}",
"options": {},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2
},
{
"id": "8d2da454-3561-4ddc-8df7-b5d535a4411f",
"name": "用户资料分析器",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-840,
0
],
"parameters": {
"text": "=You will provide a detailed user profile of an X user based on their recent tweets. Your output will serve to create new tweets for this user, that have the same feel, punctuation patterns, language hickups, emoji usage, use of abbreviation, humor and flow of story as the user. so go as detailed as possible on what you can find out, so that the next agent can work with your ouput to craft a tweet.\nhere are the user's last tweets: \n{{ $json.tweets.toJsonString() }}\n",
"options": {},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2
},
{
"id": "59bfbd43-e08b-4369-9db5-b0a24fa7312d",
"name": "关键词分析器",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-840,
320
],
"parameters": {
"text": "=You are an expert Twitter trend analyst. Analyze the user's profile and generate 3 groups of related keywords that will find relevant, high-engagement tweets for their goals.\n\nUser Profile Analysis:\n{{ $json.output.toJsonString() }}\n\nUser Additional Input: \n{{ $('On form submission').item.json[\"Additional Infos (Optional)\"] || \"none\" }}\n\nYour Mission:\nGenerate 3 groups of keywords (3-5 keywords per group) that align with the user's expertise and goals. Each group should target different aspects of their interests.\n\nFocus on:\n- Specific terminology in their field\n- Related concepts and synonyms \n- Industry jargon and buzzwords\n- Trending topics in their niche\n- Tools and technologies they use\n\nGuidelines:\n- Use exact phrases that people actually tweet about\n- Include both formal and casual variations\n- Consider abbreviations and acronyms\n- Think about what thought leaders in their field discuss\n- Focus on actionable, engaging content\n\nExample:\nIf user is into AI automation:\nGroup 1: [\"AI agents\", \"autonomous AI\", \"AI automation\", \"agent orchestration\"] \nGroup 2: [\"no code\", \"workflow automation\", \"AI tools\", \"automation tools\"]\nGroup 3: [\"cursor\", \"AI coding\", \"AI development\", \"LLM integration\"]\n\nOutput exactly 3 keyword groups that will find the most relevant tweets for engagement opportunities.",
"options": {},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2
},
{
"id": "98f3a6f6-397b-48b8-8770-0aa89720ed4b",
"name": "获取相关推文",
"type": "n8n-nodes-base.httpRequest",
"position": [
-320,
460
],
"parameters": {
"url": "https://api.twitterapi.io/twitter/tweet/advanced_search",
"options": {},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "query",
"value": "={{ $json[\"queries\"] }}"
},
{
"name": "queryType",
"value": "Top"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"id": "ptKPKaEhgzwgwNXi",
"name": "Header Auth account"
}
},
"typeVersion": 4.2
},
{
"id": "8c513d94-3aec-4094-8989-572cb019a258",
"name": "点击显示结果",
"type": "n8n-nodes-base.html",
"position": [
600,
440
],
"parameters": {
"html": "{{ $json.html }}"
},
"typeVersion": 1.2
},
{
"id": "0d75fcdf-83d4-4eed-b5b7-84521948022e",
"name": "替换长破折号",
"type": "n8n-nodes-base.code",
"position": [
600,
0
],
"parameters": {
"jsCode": "const items = [];\n\nfor (const item of $input.all()) {\n const data = item.json;\n \n if (!data.output || !data.output.tweets || !Array.isArray(data.output.tweets)) {\n items.push({\n json: data\n });\n continue;\n }\n \n const processedTweets = data.output.tweets.map(tweet => {\n return {\n ...tweet,\n text: tweet.text ? tweet.text.replace(/--/g, ' - ') : tweet.text\n };\n });\n \n items.push({\n json: {\n ...data,\n output: {\n ...data.output,\n tweets: processedTweets\n }\n }\n });\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "4478ff3c-b653-4e4d-bd03-2b10b13b8265",
"name": "组合关键词",
"type": "n8n-nodes-base.code",
"position": [
-320,
0
],
"parameters": {
"jsCode": "const items = [];\n\nfor (const item of $input.all()) {\n const data = item.json;\n const userGoals = $('On form submission').item.json[\"What are your current goals on X\"];\n \n const keywordGroups = data.output.keyword_groups || [];\n \n const wantsViral = userGoals && userGoals.includes(\"Write a viral tweet\");\n const wantsFollowers = userGoals && userGoals.includes(\"Increase followers\");\n \n const queries = [];\n \n keywordGroups.forEach((group, index) => {\n const keywordQuery = group.map(keyword => `\"${keyword}\"`).join(\" OR \");\n \n let searchQuery = `(${keywordQuery})`;\n \n searchQuery += \" within_time:2d\";\n \n if (wantsViral) {\n searchQuery += \" min_faves:200 min_retweets:20\";\n } else if (wantsFollowers) {\n searchQuery += \" min_faves:100 min_retweets:10\";\n } else {\n searchQuery += \" min_faves:50\";\n }\n \n searchQuery += \" lang:en -$ -filter:retweets\";\n \n if (index === 0) {\n searchQuery += \" filter:verified\";\n } else if (index === 1) {\n searchQuery += \" filter:blue_verified\";\n }\n \n queries.push(searchQuery);\n });\n \n items.push({\n json: {\n queries: queries,\n user_goals: userGoals,\n wants_viral: wantsViral,\n original_keyword_groups: keywordGroups\n }\n });\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "61f53128-9ef7-4638-81ff-a752a62510a4",
"name": "o3-mini",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-840,
180
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "o3-mini",
"cachedResultName": "o3-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "q4srYaXuPZeNbtbt",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
},
{
"id": "de12f5b8-38b5-41b8-bfb8-756f40e98668",
"name": "资料架构",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
-660,
180
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"examplePrompt\": {\n \"type\": \"string\",\n \"description\": \"This should be one of their best tweets (just as is, nothing changed)\"\n },\n \"punctuation\": {\n \"type\": \"string\",\n \"description\": \"User's punctuation style and patterns\"\n },\n \"personalityType\": {\n \"type\": \"string\",\n \"enum\": [\n \"Extraverted sensation\",\n \"Introverted sensation\", \n \"Extraverted intuition\",\n \"Introverted intuition\",\n \"Extraverted thinking\",\n \"Introverted thinking\",\n \"Extraverted feeling\",\n \"Introverted feeling\"\n ],\n \"description\": \"Dominant cognitive function based on user's communication style\"\n },\n \"humorType\": {\n \"type\": \"string\",\n \"enum\": [\n \"Physical comedy or slapstick comedy\",\n \"Aggressive humor\",\n \"Self-enhancing humor\", \n \"Self-defeating humor\",\n \"Affiliative humor\",\n \"Self-deprecating humor\",\n \"Observational humor\",\n \"Dry humor\"\n ],\n \"description\": \"Primary humor style used by the user\"\n },\n \"fieldOfExpertise\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"maxItems\": 5,\n \"description\": \"User's areas of expertise (up to 5)\"\n },\n \"recognizedPatterns\": {\n \"type\": \"string\",\n \"description\": \"This could be something like 'user always starts with this', or 'user often uses the pray emoji', etc.\"\n }\n },\n \"required\": [\"examplePrompt\"],\n \"additionalProperties\": false\n}"
},
"typeVersion": 1.2
},
{
"id": "3ed5bc68-5456-4fd5-a1d6-d3c2fa05fc94",
"name": "4.1-mini",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-840,
500
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini",
"cachedResultName": "gpt-4.1-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "q4srYaXuPZeNbtbt",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
},
{
"id": "592ede7f-1a72-480a-8090-740906cb9d21",
"name": "关键词架构",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
-660,
500
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"keyword_groups\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"array\", \n \"items\": {\n \"type\": \"string\"\n },\n \"minItems\": 3,\n \"maxItems\": 5,\n \"description\": \"A group of 3-5 related keywords that will be combined with OR\"\n },\n \"minItems\": 3,\n \"maxItems\": 3,\n \"description\": \"3 groups of related keywords, each targeting different aspects of user's interests\"\n }\n },\n \"required\": [\"keyword_groups\"],\n \"additionalProperties\": false\n}"
},
"typeVersion": 1.2
},
{
"id": "debdaf4b-4c3e-4797-b1e4-1b0f118175e7",
"name": "o3-mini1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
60,
500
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "o3-mini",
"cachedResultName": "o3-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "q4srYaXuPZeNbtbt",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
},
{
"id": "8745739b-1a0f-4390-accb-8127c5602f0e",
"name": "推文架构",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
260,
500
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"tweets\": {\n \"type\": \"array\",\n \"minItems\": 5,\n \"maxItems\": 10,\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"priority\": {\n \"type\": \"integer\",\n \"minimum\": 1,\n \"maximum\": 5,\n \"description\": \"Priority level from 1-5 (1 is highest priority, 5 is lowest priority)\"\n },\n \"text\": {\n \"type\": \"string\",\n \"description\": \"The tweet text content\"\n },\n \"isAnswer\": {\n \"type\": \"boolean\",\n \"description\": \"If true, the tweet is a response to one of the listed tweets; if false, it's an original tweet by the user\"\n },\n \"tweetUrl\": {\n \"type\": \"string\",\n \"description\": \"The URL to the tweet to respond to (required only if isAnswer is true)\"\n },\n \"reasoning\": {\n \"type\": \"string\",\n \"description\": \"Concise reason why this tweet will bring the user closer to their goal\"\n }\n },\n \"required\": [\"priority\", \"text\", \"isAnswer\", \"reasoning\"],\n \"additionalProperties\": false,\n \"if\": {\n \"properties\": {\n \"isAnswer\": {\n \"const\": true\n }\n }\n },\n \"then\": {\n \"required\": [\"priority\", \"text\", \"isAnswer\", \"tweetUrl\", \"reasoning\"]\n }\n },\n \"description\": \"Array of 1-5 tweet suggestions with priority and context\"\n }\n },\n \"required\": [\"tweets\"],\n \"additionalProperties\": false\n}"
},
"typeVersion": 1.2
},
{
"id": "fb0151fe-e279-4a7e-b60c-0c318c16bc91",
"name": "准备结果",
"type": "n8n-nodes-base.code",
"position": [
600,
220
],
"parameters": {
"jsCode": "const items = [];\n\nfor (const item of $input.all()) {\n const data = item.json;\n \n const aggregateNodeData = $('Aggregate').all();\n \n const originalTweetsMap = {};\n \n let debugUrls = [];\n \n if (aggregateNodeData && aggregateNodeData.length > 0) {\n aggregateNodeData.forEach((nodeItem, nodeIndex) => {\n if (nodeItem.json && nodeItem.json.tweets) {\n \n if (Array.isArray(nodeItem.json.tweets)) {\n nodeItem.json.tweets.forEach((tweetGroup, groupIndex) => {\n if (Array.isArray(tweetGroup)) {\n tweetGroup.forEach((tweet, tweetIndex) => {\n if (tweet && typeof tweet === 'object') {\n let username = 'unknown';\n let authorName = 'Unknown User';\n \n if (tweet.author) {\n username = tweet.author.username || tweet.author.screen_name || tweet.author.handle || 'unknown';\n authorName = tweet.author.name || tweet.author.display_name || 'Unknown User';\n }\n \n if (!username || username === 'unknown') {\n username = tweet.username || tweet.screen_name || tweet.handle || 'unknown';\n }\n if (!authorName || authorName === 'Unknown User') {\n authorName = tweet.author_name || tweet.name || 'Unknown User';\n }\n \n const tweetData = {\n text: tweet.text,\n author: username,\n authorName: authorName\n };\n \n if (tweet.url && tweet.text) {\n originalTweetsMap[tweet.url] = tweetData;\n debugUrls.push(tweet.url);\n }\n if (tweet.twitterUrl && tweet.text) {\n originalTweetsMap[tweet.twitterUrl] = tweetData;\n debugUrls.push(tweet.twitterUrl);\n }\n \n if (tweet.url) {\n const normalizedUrl = tweet.url.replace('https://x.com', 'https://twitter.com');\n originalTweetsMap[normalizedUrl] = tweetData;\n }\n if (tweet.twitterUrl) {\n const normalizedUrl = tweet.twitterUrl.replace('https://twitter.com', 'https://x.com');\n originalTweetsMap[normalizedUrl] = tweetData;\n }\n }\n });\n } else if (tweetGroup && typeof tweetGroup === 'object') {\n let username = 'unknown';\n let authorName = 'Unknown User';\n \n if (tweetGroup.author) {\n username = tweetGroup.author.username || tweetGroup.author.screen_name || tweetGroup.author.handle || 'unknown';\n authorName = tweetGroup.author.name || tweetGroup.author.display_name || 'Unknown User';\n }\n \n if (!username || username === 'unknown') {\n username = tweetGroup.username || tweetGroup.screen_name || tweetGroup.handle || 'unknown';\n }\n if (!authorName || authorName === 'Unknown User') {\n authorName = tweetGroup.author_name || tweetGroup.name || 'Unknown User';\n }\n \n const tweetData = {\n text: tweetGroup.text,\n author: username,\n authorName: authorName\n };\n \n if (tweetGroup.url && tweetGroup.text) {\n originalTweetsMap[tweetGroup.url] = tweetData;\n debugUrls.push(tweetGroup.url);\n }\n if (tweetGroup.twitterUrl && tweetGroup.text) {\n originalTweetsMap[tweetGroup.twitterUrl] = tweetData;\n debugUrls.push(tweetGroup.twitterUrl);\n }\n }\n });\n }\n }\n });\n }\n \n if (!data.output || !data.output.tweets || !Array.isArray(data.output.tweets)) {\n items.push({\n json: {\n html: '<div>No tweets data found</div>'\n }\n });\n continue;\n }\n \n const tweets = data.output.tweets;\n const responseOpportunities = tweets.filter(t => t.isAnswer);\n const originalTweets = tweets.filter(t => !t.isAnswer);\n \n // Debug: track which URLs we're looking for vs what we found\n const lookingFor = responseOpportunities.map(t => t.tweetUrl);\n const notFound = [];\n const skippedUnknown = [];\n \n let tweetsHtml = '';\n \n responseOpportunities.forEach((tweet, index) => {\n let originalTweetData = originalTweetsMap[tweet.tweetUrl];\n \n if (!originalTweetData) {\n const normalizedUrl1 = tweet.tweetUrl.replace('https://x.com', 'https://twitter.com');\n const normalizedUrl2 = tweet.tweetUrl.replace('https://twitter.com', 'https://x.com');\n originalTweetData = originalTweetsMap[normalizedUrl1] || originalTweetsMap[normalizedUrl2];\n }\n \n if (!originalTweetData || \n originalTweetData.authorName === 'Unknown User' || \n originalTweetData.text === 'Original tweet text not available') {\n skippedUnknown.push(tweet.tweetUrl);\n notFound.push(tweet.tweetUrl);\n return;\n }\n \n const originalTweetText = originalTweetData.text;\n const authorInfo = originalTweetData.authorName;\n \n const isAnswerBadge = '<span style=\"background: #8b5cf6; color: white; padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: 600; margin-bottom: 16px; display: inline-block;\">💬 Response Opportunity</span>';\n \n const tweetId = `tweet-response-${index}`;\n const reasoningId = `reasoning-response-${index}`;\n \n tweetsHtml += `\n <div style=\"border-bottom: 2px solid #e5e7eb; padding: 24px;\">\n ${isAnswerBadge}\n \n <!-- Original Tweet -->\n <div style=\"margin-bottom: 20px;\">\n <div style=\"display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;\">\n <div style=\"color: #6b7280; font-size: 14px;\">\n <strong>${authorInfo}</strong>\n </div>\n <button onclick=\"toggleReasoning('${reasoningId}')\" style=\"background: #6b7280; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 10px; transition: background 0.2s;\" onmouseover=\"this.style.background='#4b5563'\" onmouseout=\"this.style.background='#6b7280'\">\n ℹ️\n </button>\n </div>\n \n <div onclick=\"window.open('${tweet.tweetUrl}', '_blank')\" style=\"background: #f9fafb; padding: 16px; border-radius: 8px; border-left: 4px solid #1da1f2; margin-bottom: 12px; cursor: pointer; transition: background 0.2s;\" onmouseover=\"this.style.background='#f3f4f6'\" onmouseout=\"this.style.background='#f9fafb'\">\n ${originalTweetText}\n </div>\n \n <div style=\"background: #fafafa; padding: 8px 12px; border-radius: 6px; font-family: monospace; font-size: 11px; word-break: break-all; margin-bottom: 8px;\">\n <strong>URL:</strong> <a href=\"${tweet.tweetUrl}\" target=\"_blank\" style=\"color: #1da1f2; text-decoration: none;\">${tweet.tweetUrl}</a>\n </div>\n \n <!-- Hidden Reasoning -->\n <div id=\"${reasoningId}\" style=\"display: none; background: #fff3cd; padding: 12px; border-radius: 6px; margin-top: 12px; border-left: 4px solid #ffc107;\">\n <strong>Strategic Analysis:</strong> ${tweet.reasoning}\n </div>\n </div>\n\n <!-- Suggested Response -->\n <div>\n <h3 style=\"color: #374151; margin: 0 0 12px 0; font-size: 16px;\">✨ Your Response</h3>\n <div id=\"${tweetId}\" onclick=\"copyResponseText('${tweetId}')\" style=\"background: #f0fdf4; padding: 16px; border-radius: 8px; border-left: 4px solid #10b981; margin-bottom: 12px; cursor: pointer; transition: all 0.2s;\" onmouseover=\"this.style.background='#dcfce7'; this.style.transform='translateY(-1px)'; this.style.boxShadow='0 4px 12px rgba(16, 185, 129, 0.15)'\" onmouseout=\"this.style.background='#f0fdf4'; this.style.transform='translateY(0)'; this.style.boxShadow='none'\">\n ${tweet.text}\n </div>\n </div>\n \n </div>\n `;\n });\n \n originalTweets.forEach((tweet, index) => {\n const originalBadge = '<span style=\"background: #f59e0b; color: white; padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: 600; margin-bottom: 16px; display: inline-block;\">✨ Original Tweet</span>';\n \n const tweetId = `tweet-original-${index}`;\n const reasoningId = `reasoning-original-${index}`;\n \n tweetsHtml += `\n <div style=\"border-bottom: 2px solid #e5e7eb; padding: 24px;\">\n ${originalBadge}\n \n <!-- Original Tweet Content -->\n <div style=\"margin-bottom: 20px;\">\n <div style=\"display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;\">\n <h3 style=\"color: #374151; margin: 0; font-size: 16px;\">🎯 Your Original Tweet</h3>\n <button onclick=\"toggleReasoning('${reasoningId}')\" style=\"background: #6b7280; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 10px; transition: background 0.2s;\" onmouseover=\"this.style.background='#4b5563'\" onmouseout=\"this.style.background='#6b7280'\">\n ℹ️\n </button>\n </div>\n \n <div id=\"${tweetId}\" onclick=\"copyResponseText('${tweetId}')\" style=\"background: #fef3c7; padding: 16px; border-radius: 8px; border-left: 4px solid #f59e0b; margin-bottom: 12px; cursor: pointer; transition: all 0.2s;\" onmouseover=\"this.style.background='#fde68a'; this.style.transform='translateY(-1px)'; this.style.boxShadow='0 4px 12px rgba(245, 158, 11, 0.15)'\" onmouseout=\"this.style.background='#fef3c7'; this.style.transform='translateY(0)'; this.style.boxShadow='none'\">\n ${tweet.text}\n </div>\n \n <!-- Hidden Reasoning for Original Tweet -->\n <div id=\"${reasoningId}\" style=\"display: none; background: #ede9fe; padding: 12px; border-radius: 6px; margin-top: 12px; border-left: 4px solid #8b5cf6;\">\n <strong>Strategic Reasoning:</strong> ${tweet.reasoning}\n </div>\n </div>\n \n </div>\n `;\n });\n\n let debugSection = '';\n if (notFound.length > 0) {\n debugSection = `\n <div style=\"padding: 20px; background: #fef2f2; border-top: 1px solid #fecaca; margin-top: 20px;\">\n <details style=\"font-size: 12px; color: #dc2626;\">\n <summary style=\"cursor: pointer; font-weight: 600;\">🔍 Debug: ${notFound.length} tweets not shown</summary>\n <div style=\"margin-top: 8px;\">\n <p><strong>Total response opportunities:</strong> ${responseOpportunities.length}</p>\n <p><strong>Shown:</strong> ${responseOpportunities.length - notFound.length}</p>\n <p><strong>Hidden (unknown users/missing data):</strong> ${notFound.length}</p>\n <p><strong>Original tweets shown:</strong> ${originalTweets.length}</p>\n <br>\n <p><strong>URLs we're looking for:</strong></p>\n <div style=\"font-family: monospace; font-size: 10px; background: white; padding: 8px; border-radius: 4px; margin: 4px 0;\">\n ${lookingFor.map(url => `• ${url}`).join('<br>')}\n </div>\n <p><strong>URLs we found in data:</strong></p>\n <div style=\"font-family: monospace; font-size: 10px; background: white; padding: 8px; border-radius: 4px; margin: 4px 0;\">\n ${debugUrls.length > 0 ? debugUrls.map(url => `• ${url}`).join('<br>') : 'No URLs found in aggregate data'}\n </div>\n <p><strong>Skipped URLs (unknown/missing):</strong></p>\n <div style=\"font-family: monospace; font-size: 10px; background: white; padding: 8px; border-radius: 4px; margin: 4px 0;\">\n ${skippedUnknown.map(url => `• ${url}`).join('<br>')}\n </div>\n </div>\n </details>\n </div>\n `;\n }\n\n const totalShown = (responseOpportunities.length - notFound.length) + originalTweets.length;\n \n const fullHtml = `\n <div style=\"max-width: 800px; margin: 20px auto; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8fafc; padding: 20px;\">\n <div style=\"background: white; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); overflow: hidden;\">\n \n <div style=\"background: #000000; color: white; padding: 20px; text-align: center;\">\n <h1 style=\"margin: 0; font-size: 24px;\">🚀 X Engagement Opportunities</h1>\n <p style=\"margin: 10px 0 0 0; opacity: 0.9;\">Strategic responses and original content (${totalShown} suggestions)</p>\n </div>\n\n ${tweetsHtml}\n ${debugSection}\n\n </div>\n </div>\n\n <script>\n function copyResponseText(elementId) {\n const element = document.getElementById(elementId);\n const text = element.textContent || element.innerText;\n \n if (navigator.clipboard && window.isSecureContext) {\n navigator.clipboard.writeText(text.trim()).then(function() {\n showNotification('✅ Tweet copied to clipboard!');\n flashCopySuccess(elementId);\n }).catch(function(err) {\n fallbackCopy(text.trim());\n });\n } else {\n fallbackCopy(text.trim());\n }\n }\n \n function fallbackCopy(text) {\n const textArea = document.createElement('textarea');\n textArea.value = text;\n textArea.style.position = 'fixed';\n textArea.style.left = '-999999px';\n textArea.style.top = '-999999px';\n document.body.appendChild(textArea);\n textArea.focus();\n textArea.select();\n \n try {\n document.execCommand('copy');\n showNotification('✅ Tweet copied to clipboard!');\n } catch (err) {\n showNotification('❌ Copy failed - please select and copy manually');\n }\n \n document.body.removeChild(textArea);\n }\n \n function flashCopySuccess(elementId) {\n const element = document.getElementById(elementId);\n const originalBg = element.style.backgroundColor;\n element.style.backgroundColor = '#10b981';\n element.style.color = 'white';\n \n setTimeout(() => {\n element.style.backgroundColor = originalBg;\n element.style.color = '';\n }, 200);\n }\n\n function toggleReasoning(elementId) {\n const element = document.getElementById(elementId);\n if (element.style.display === 'none') {\n element.style.display = 'block';\n } else {\n element.style.display = 'none';\n }\n }\n\n function showNotification(message) {\n const notification = document.createElement('div');\n notification.textContent = message;\n notification.style.cssText = 'position:fixed;top:20px;right:20px;background:#10b981;color:white;padding:12px 20px;border-radius:6px;z-index:1000;font-size:14px;box-shadow:0 4px 12px rgba(0,0,0,0.15); animation: slideIn 0.3s ease;';\n document.body.appendChild(notification);\n \n setTimeout(() => {\n if (document.body.contains(notification)) {\n notification.style.animation = 'slideOut 0.3s ease';\n setTimeout(() => {\n if (document.body.contains(notification)) {\n document.body.removeChild(notification);\n }\n }, 300);\n }\n }, 2000);\n }\n \n // Add CSS for animations\n const style = document.createElement('style');\n style.textContent = \\`\n @keyframes slideIn {\n from { transform: translateX(100%); opacity: 0; }\n to { transform: translateX(0); opacity: 1; }\n }\n @keyframes slideOut {\n from { transform: translateX(0); opacity: 1; }\n to { transform: translateX(100%); opacity: 0; }\n }\n \\`;\n document.head.appendChild(style);\n </script>\n `;\n\n items.push({\n json: {\n html: fullHtml,\n tweet_count: totalShown,\n response_opportunities: responseOpportunities.length - notFound.length,\n original_tweets: originalTweets.length,\n original_data: data,\n debug_missing_tweets: notFound,\n debug_skipped_unknown: skippedUnknown\n }\n });\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "9306ec98-229c-44df-9be4-eb642eb49a35",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1840,
0
],
"parameters": {
"color": 7,
"width": 500,
"height": 660,
"content": "## 📋 设置说明"
},
"typeVersion": 1
},
{
"id": "39c40eba-dbf9-449e-b80c-147625ff4acc",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-580,
-240
],
"parameters": {
"color": 7,
"width": 600,
"height": 80,
"content": "# 🚀 X 推文建议工作流"
},
"typeVersion": 1
}
],
"active": false,
"scopes": [
"workflow:create",
"workflow:delete",
"workflow:execute",
"workflow:list",
"workflow:move",
"workflow:read",
"workflow:share",
"workflow:update"
],
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"createdAt": "2025-06-07T04:31:44.088Z",
"updatedAt": "2025-06-10T05:43:37.000Z",
"versionId": "27278b7e-2af0-47d1-b904-8d25bbd300e2",
"isArchived": false,
"staticData": null,
"connections": {
"o3-mini": {
"ai_languageModel": [
[
{
"node": "User Profile Analyser",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"4.1-mini": {
"ai_languageModel": [
[
{
"node": "Keyword Analyser",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"o3-mini1": {
"ai_languageModel": [
[
{
"node": "Tweet Writer",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "Tweet Writer",
"type": "main",
"index": 0
}
]
]
},
"Split Out": {
"main": [
[
{
"node": "Get relevant tweets",
"type": "main",
"index": 0
}
]
]
},
"Map Tweets": {
"main": [
[
{
"node": "User Profile Analyser",
"type": "main",
"index": 0
}
]
]
},
"Tweet Writer": {
"main": [
[
{
"node": "Replace em dash",
"type": "main",
"index": 0
}
]
]
},
"tweet schema": {
"ai_outputParser": [
[
{
"node": "Tweet Writer",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Prepare Result": {
"main": [
[
{
"node": "Click to show Result",
"type": "main",
"index": 0
}
]
]
},
"profile schema": {
"ai_outputParser": [
[
{
"node": "User Profile Analyser",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Get User Tweets": {
"main": [
[
{
"node": "Map Tweets",
"type": "main",
"index": 0
}
]
]
},
"Replace em dash": {
"main": [
[
{
"node": "Prepare Result",
"type": "main",
"index": 0
}
]
]
},
"keywords schema": {
"ai_outputParser": [
[
{
"node": "Keyword Analyser",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Keyword Analyser": {
"main": [
[
{
"node": "combine keywords",
"type": "main",
"index": 0
}
]
]
},
"combine keywords": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
}
]
]
},
"On form submission": {
"main": [
[
{
"node": "Get User Tweets",
"type": "main",
"index": 0
}
]
]
},
"Get relevant tweets": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"User Profile Analyser": {
"main": [
[
{
"node": "Keyword Analyser",
"type": "main",
"index": 0
}
]
]
}
},
"triggerCount": 0
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 人工智能, 营销
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
(Duc)深度研究市场模板
集成PerplexityAI研究和OpenAI内容的多层级WordPress博客生成器
If
Set
Xml
+28
132 节点Daniel Ng
人工智能
基于AI的YouTube元数据生成器(GPT-4o、Gemini与内容增强)
基于AI的YouTube元数据生成器(GPT-4o、Gemini与内容增强)
Set
Xml
Code
+12
37 节点Amjid Ali
人工智能
WordPress 内容生成器 v3
WordPress 内容生成器 v3
If
Set
Code
+21
102 节点Alex Kim
人工智能
AI Logo表格提取器到Airtable
使用表单、AI、Google Sheet和Airtable从Logo表格中提取信息
Set
Code
Html
+11
44 节点Marcel Claus-Ahrens
销售
WordPress博客自动化专业版(深度研究)v1
WordPress自动博客专业版 - 含深度研究的内容自动化机器
If
Set
Xml
+24
77 节点Daniel Ng
人工智能
✨🤖 自动化AI驱动的社交媒体内容工厂,适用于X + Facebook + Instagram + LinkedIn
✨🤖 使用AI自动化多平台社交媒体内容创建
If
Set
Code
+17
57 节点Joseph LePage
人工智能
工作流信息
难度等级
高级
节点数量21
分类2
节点类型10
作者
@OnePromptMagic
@one-prompt-magicdegree in computer science software dev, specialised in AI/LLM integrations CEO psquared GmbH (psquared.dev) -> we do custom Agent Implementation and process automation (e.g using n8n) not every flow needs AI in it -> only using it very it really provides value.
外部链接
在 n8n.io 查看 →
分享此工作流