소개
Threads에 본문과 댓글을 이어서 자동으로 포스팅하는 워크플로우를 만들었습니다.
기존에는 본문과 댓글을 각각 복사-붙여넣기 하다 보니 실수도 많고 반복도 많았는데, 만들고 보니 작업이 무척 편해졌습니다.
Threads 글 소스는 이전에 만들었던 자동화 시스템(n8n 롱폼 블로그 작성 자동화 #3)에서 생성된 콘텐츠입니다.
https://www.gpters.org/nocode/post/n8n-long-foam-blog-ydRoK7ooB8ZPx7F
진행 방법
전체 흐름은 아래와 같습니다:
1. Schedule Trigger
정해진 시간마다 워크플로우를 시작합니다.
2. Get row(s) in sheet1
구글 시트에서 아직 처리되지 않은 블로그 글을 불러옵니다.
threadYes = 'Yes' 인 row를 호출
3. Loop Over Items
여러 개의 블로그 글이 있을 경우, 한 줄씩 반복 처리합니다.
4. Replace Me (전처리용 노드)
프롬프트에서 사용할 수 있게 텍스트를 정제합니다.
5. Basic LLM Chain2 (Gemini)
입력된 블로그 글을 바탕으로 Threads 포스팅 형식(JSON: topic_tag, body, reply)을 생성합니다. (위 2번 노드의 H열이 입력된 블로그 글)
이전에 설계한 시스템/user 프롬프트와 출력 스키마를 사용했습니다.
User Prompt
너는 쓰레드 전문가다. 너는 긴 유튜브 대본을 하나의 Threads용 인기 포스트로 축약 정리할 JSON만 출력한다. JSON 외의 텍스트, 백틱, 설명, 주석, 아무것도 절대 출력하지 마. 작성 규칙: - body: 사람들의 관심을 끌 수 있는 내용 중심 * 충격적이거나 놀라운 사실 * 호기심을 자극하는 질문이나 상황 * 트렌드나 화제성 있는 내용 * 개인적 경험이나 감정적 공감대 - reply: body글에 이어서 핵심 내용 작성 * body에서 제기한 내용의 구체적 설명 * 실용적이고 유용한 정보 * 기술적 디테일이나 방법론 * actionable한 팁이나 가이드 **주의사항**: * 본문과 댓글의 내용이 자연스럽게 이어져야 해. * 이모지는 과하지 않게, 내용의 흐름을 방해하지 않는 선에서 사용해. * 핵심 해시태그를 포함하되, 남발하지 않도록 유의해. * 내용의 전문성은 유지하되, 독자가 쉽게 이해할 수 있도록 쉽게 풀어 써줘. - 캐주얼하고 친근한 톤 (반말 사용) - 이모지를 문장 시작이나 강조 포인트에 활용 - 해시태그는 트렌딩 키워드 중심으로 2-3개 - 줄바꿈으로 가독성 극대화 출력 스키마(수정 금지): { "topic_tag": "주제명_키워드", "body": "🔥 충격적이거나 호기심 유발하는 첫 문장\n\n💭 사람들이 궁금해할 만한 내용\n감정적 공감대나 개인적 경험\n\n🤔 호기심을 자극하는 마무리\n\n#해시태그1 #해시태그2 #해시태그3", "reply": "👆 위에서 말한 그 내용은...\n\n💡 구체적인 핵심 설명\n기술적 디테일이나 방법\n\n⚡ 실용적인 팁이나 가이드\n즉시 적용 가능한 내용\n\n#해시태그1 #해시태그2" } --- 다음 YouTube 블로그 글을 읽고, 위 가이드라인에 맞춰 **body**와 **reply**를 생성해 주세요. --- {{ $json.blogText }}System Prompt
너는 Threads 인기글 작성 전문가다. 유튜브 블로그 글을 받아서 Threads용 포스트로 변환한다. 반드시 JSON 형식으로만 출력하고, 다른 텍스트는 절대 포함하지 마.
6. Code
LLM이 반환한 raw JSON 문자열에서
body,reply,topic_tag필드를 분리합니다.위 Prompt에서 분리하고자 했으나, 분리에 실패해서 부득불 Code 노드로 해결
" ☞ 댓글에서 확인" 문구를 본문(body) 마지막 에 삽입.
댓글로 유도하기 위해서 본문(body) -> 댓글(reply) 두 개 글(포스팅) 구조화
// AI 응답에서 JSON 추출 및 파싱 const inputData = $input.first().json; let aiResponse = inputData.text || inputData.content || inputData.output || ''; // JSON 문자열 정리 (백틱, 마크다운, 불필요한 문자 제거) let cleanJson = aiResponse .replace(/```json\n?/g, '') .replace(/```\n?/g, '') .replace(/^[^{]*/, '') // JSON 시작 전 문자 제거 .replace(/[^}]*$/, '') // JSON 끝 후 문자 제거 .trim(); // Body 텍스트에 "☞ 댓글에서 확인" 추가하는 함수 function addCommentPrompt(bodyText) { if (!bodyText) return bodyText; // \n\n으로 분리 const parts = bodyText.split('\n\n'); if (parts.length >= 2) { // 마지막 부분이 해시태그인지 확인 const lastPart = parts[parts.length - 1]; if (lastPart.startsWith('#')) { // 마지막에서 두 번째 부분(문장)에 " ☞ 댓글에서 확인" 추가 parts[parts.length - 2] = parts[parts.length - 2] + ' ☞ 댓글에서 확인'; return parts.join('\n\n'); } } return bodyText + ' ☞ 댓글에서 확인'; } try { // JSON 파싱 const parsed = JSON.parse(cleanJson); // Body에 댓글 안내 문구 추가 const modifiedBody = addCommentPrompt(parsed.body || "body 내용을 찾을 수 없습니다"); // 개별 필드로 분리하여 출력 return [{ json: { topic_tag: parsed.topic_tag || "파싱_실패", body: modifiedBody, reply: parsed.reply || parsed.reply1 || "reply 내용을 찾을 수 없습니다", original_response: aiResponse // 디버깅용 } }]; } catch (error) { // 파싱 실패시 원본 텍스트에서 수동 추출 시도 console.log("JSON 파싱 실패, 수동 추출 시도:", error.message); const topicMatch = aiResponse.match(/"topic_tag":\s*"([^"]+)"/); const bodyMatch = aiResponse.match(/"body":\s*"([^"]+(?:\\.[^"]*)*?)"/); const replyMatch = aiResponse.match(/"reply":\s*"([^"]+(?:\\.[^"]*)*?)"/); const extractedBody = bodyMatch ? bodyMatch[1].replace(/\\n/g, '\n') : "body 추출 실패"; const modifiedBody = addCommentPrompt(extractedBody); return [{ json: { topic_tag: topicMatch ? topicMatch[1] : "추출_실패", body: modifiedBody, reply: replyMatch ? replyMatch[1].replace(/\\n/g, '\n') : "reply 추출 실패", error: error.message, original_response: aiResponse } }]; }
7. Update row in sheet
생성한 body글과 reply글을 구글 시트에 기록(2번 노드 구글 시트의 K열, L열)
8. 토큰 행 가지고 오기
Threads API 호출에 필요한 액세스 토큰 정보를 불러옵니다.
토큰 저장하는 워크플로우 : https://www.gpters.org/nocode/post/n8nthreads-posting-automation-1-TKoYHOclxLZv6Si
9. 쓰레드 포스팅 셋팅
API 호출에 필요한 값을 Set 노드에서 준비합니다.
10. Body
body텍스트를 Threads에 게시합니다. 이때 새 스레드로 올라갑니다.
11. Reply
중요: 이 노드는 Body가 만든 스레드 아래에 댓글을 달도록 구성돼야 합니다.
초반엔 이 Reply도 새로운 스레드로 올라가는 문제가 있었고, 그걸 해결하기 위해
reply_to_id를 추가했습니다.이 ID는 body >
id필드를 가져오면 됩니다.
결과와 배운 점
✅ 배운 점
Threads API는 reply 요청도
/threads엔드포인트를 쓰지만,reply_to_id파라미터를 줘야 댓글로 인식됩니다.auto_publish_text옵션을 true로 설정하면 게시글이 임시 저장 없이 바로 공개됩니다. Threads API에서는 이 옵션을 기본값 false로 두는 경우, 포스트가 비공개 상태로 올라갈 수 있기 때문에 자동화 시 반드시 true로 설정해야 안정적인 게시가 가능합니다.
😵 시행착오
댓글이 본문으로 따로 올라오는 문제: reply가 아닌 새글로 등록됨 →
reply_to_id해결댓글을 Threads에 달려면 본문(post) ID가 정확해야 합니다. 그렇지 않으면 reply로 생성된 글을 새로운 본문으로 올려버림. 그 해결책은 본문을 올린 뒤에 응답으로 받은
id값을 새로운 parameter로서reply_to_id를 생성하고 id값을 정확히 전달하는 것. 이걸 놓치면 댓글이 아예 등록되지 않거나 별도 포스트로 올라갑니다.
LLM에서 바로 JSON 형태로
topic_tag,body,reply를 출력하도록 프롬프트를 구성했으나, 실제로는 백틱(```json)이 포함되거나 문자열 파싱 오류가 발생했습니다. 결국 안정적인 파싱을 위해Code` 노드를 하나 추가하여, raw 텍스트에서 백틱 제거 → JSON.parse() 처리 → 필드 분리 작업을 수행하도록 했습니다.LLM 출력값을 안정적으로 분리하려면
Function노드에서 JSON 파싱 처리를 직접 해주는 게 좋습니다.
🎯 자동화 후 효과
Threads 포스팅에 소요되는 시간이 엄청 줄어들 것으로 예상
Gemini의 글쏨씨가 대박.