X 트윗 제안
고급
이것은AI, Marketing분야의자동화 워크플로우로, 21개의 노드를 포함합니다.주로 Code, Html, SplitOut, Aggregate, FormTrigger 등의 노드를 사용하며인공지능 기술을 결합하여 스마트 자동화를 구현합니다. 개인 프로필을 기반으로 관련 X 트윗을 찾아 답변 제안을 제공
사전 요구사항
- •대상 API의 인증 정보가 필요할 수 있음
- •OpenAI API Key
사용된 노드 (21)
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
"id": "kMKlrWsoDftOxsqE",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "X Tweet Suggestions",
"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": "## 📋 Setup Instructions\n\n### 1️⃣ Configure API Keys\n- Add your OpenAI credentials to the nodes\n\n- Get your twitterapi key from https://twitterapi.io?ref=1PROMPT (:pray:)\n- Add to \"Header Auth account\" credential\n- Used for fetching user tweets and trending content\n\n### 2️⃣ How to Use\n1. **Execute** the workflow\n2. **Fill out the form** with:\n - Your X username (from your URL)\n - Your goals on X (multiple selection)\n - Optional additional info\n3. **Submit** the form\n4. **Wait** for processing (2-3 minutes)\n5. **Double-click** the \"Click to show Result\" node\n\n### 3️⃣ What This Workflow Does\n\n**Step 1:** Analyzes your recent tweets for personality & style\n**Step 2:** Generates strategic keywords based on your profile\n**Step 3:** Searches for trending tweets in your niche\n**Step 4:** Creates personalized responses & original tweets\n**Step 5:** Displays results in beautiful HTML format\n\nFollow https://x.com/OnePromptMagic for more free n8n templates\n"
},
"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 Tweet Suggestions Workflow"
},
"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": {
"61f53128-9ef7-4638-81ff-a752a62510a4": {
"ai_languageModel": [
[
{
"node": "8d2da454-3561-4ddc-8df7-b5d535a4411f",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"3ed5bc68-5456-4fd5-a1d6-d3c2fa05fc94": {
"ai_languageModel": [
[
{
"node": "59bfbd43-e08b-4369-9db5-b0a24fa7312d",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"debdaf4b-4c3e-4797-b1e4-1b0f118175e7": {
"ai_languageModel": [
[
{
"node": "0511ce11-d316-4598-9e49-9a4df3e8dc95",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"445f99d3-95ec-4c94-95b7-c64bb0783da1": {
"main": [
[
{
"node": "0511ce11-d316-4598-9e49-9a4df3e8dc95",
"type": "main",
"index": 0
}
]
]
},
"ce05506b-fba3-40ff-a28f-24434175f6b7": {
"main": [
[
{
"node": "98f3a6f6-397b-48b8-8770-0aa89720ed4b",
"type": "main",
"index": 0
}
]
]
},
"299a1ada-646c-453c-85e5-c340e6df2dfc": {
"main": [
[
{
"node": "8d2da454-3561-4ddc-8df7-b5d535a4411f",
"type": "main",
"index": 0
}
]
]
},
"0511ce11-d316-4598-9e49-9a4df3e8dc95": {
"main": [
[
{
"node": "0d75fcdf-83d4-4eed-b5b7-84521948022e",
"type": "main",
"index": 0
}
]
]
},
"8745739b-1a0f-4390-accb-8127c5602f0e": {
"ai_outputParser": [
[
{
"node": "0511ce11-d316-4598-9e49-9a4df3e8dc95",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"fb0151fe-e279-4a7e-b60c-0c318c16bc91": {
"main": [
[
{
"node": "8c513d94-3aec-4094-8989-572cb019a258",
"type": "main",
"index": 0
}
]
]
},
"de12f5b8-38b5-41b8-bfb8-756f40e98668": {
"ai_outputParser": [
[
{
"node": "8d2da454-3561-4ddc-8df7-b5d535a4411f",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"5fc895f6-b188-4900-81c7-b0eeebd1d571": {
"main": [
[
{
"node": "299a1ada-646c-453c-85e5-c340e6df2dfc",
"type": "main",
"index": 0
}
]
]
},
"0d75fcdf-83d4-4eed-b5b7-84521948022e": {
"main": [
[
{
"node": "fb0151fe-e279-4a7e-b60c-0c318c16bc91",
"type": "main",
"index": 0
}
]
]
},
"592ede7f-1a72-480a-8090-740906cb9d21": {
"ai_outputParser": [
[
{
"node": "59bfbd43-e08b-4369-9db5-b0a24fa7312d",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"59bfbd43-e08b-4369-9db5-b0a24fa7312d": {
"main": [
[
{
"node": "4478ff3c-b653-4e4d-bd03-2b10b13b8265",
"type": "main",
"index": 0
}
]
]
},
"4478ff3c-b653-4e4d-bd03-2b10b13b8265": {
"main": [
[
{
"node": "ce05506b-fba3-40ff-a28f-24434175f6b7",
"type": "main",
"index": 0
}
]
]
},
"72b79ff2-055b-4842-8a60-7b8cef9dedd0": {
"main": [
[
{
"node": "5fc895f6-b188-4900-81c7-b0eeebd1d571",
"type": "main",
"index": 0
}
]
]
},
"98f3a6f6-397b-48b8-8770-0aa89720ed4b": {
"main": [
[
{
"node": "445f99d3-95ec-4c94-95b7-c64bb0783da1",
"type": "main",
"index": 0
}
]
]
},
"8d2da454-3561-4ddc-8df7-b5d535a4411f": {
"main": [
[
{
"node": "59bfbd43-e08b-4369-9db5-b0a24fa7312d",
"type": "main",
"index": 0
}
]
]
}
},
"triggerCount": 0
}자주 묻는 질문
이 워크플로우를 어떻게 사용하나요?
위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.
이 워크플로우는 어떤 시나리오에 적합한가요?
고급 - 인공지능, 마케팅
유료인가요?
이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.
관련 워크플로우 추천
AI 기반 YouTube 메타데이터 생성기(GPT-4o, Gemini 및 콘텐츠 향상)
AI 기반 YouTube 메타데이터 생성기(GPT-4o, Gemini 및 콘텐츠 향상)
Set
Xml
Code
+
Set
Xml
Code
37 노드Amjid Ali
인공지능
WordPress 콘텐츠 생성기 v3
WordPress 내용 생성기 v3
If
Set
Code
+
If
Set
Code
102 노드Alex Kim
인공지능
AI 로고 테이블 추출기를 Airtable로
폼、AI、Google Sheet과 Airtable을 사용하여 로고 테이블에서 정보를 추출
Set
Code
Html
+
Set
Code
Html
44 노드Marcel Claus-Ahrens
영업
✨🤖 X + Facebook + Instagram + LinkedIn에 적합한 자동화 AI 추동 소셜 미디어 콘텐츠 공장
✨🤖 AI를 사용하여 다중 플랫폼 소셜 미디어 콘텐츠 생성 자동화
If
Set
Code
+
If
Set
Code
57 노드Joseph LePage
인공지능
[템플릿] AI 반려동물 가게 v8
🐶 AI 펫 샵 어시스턴트 - GPT-4o, Google 캘린더 및 WhatsApp/Instagram/Facebook 통합
If
N8n
Set
+
If
N8n
Set
244 노드Amanda Benks
영업
🗞️ AI로운 지속 가능한 마케팅 브리핑(gmail, GPT-4o 사용)
🗞️ AI 주도의 지속 가능성 마케팅 브리핑( Gmail, GPT-4o 사용)
If
Set
Code
+
If
Set
Code
21 노드Samir Saci
인공지능
워크플로우 정보
난이도
고급
노드 수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에서 보기 →
이 워크플로우 공유