Files
news-summary-bot/docs/n8n-setup.md
sm4640 9c2bf7c1ce
All checks were successful
news-summary-bot-cicd / build_push_deploy (push) Successful in 9m14s
Fix: [3.0.2] 요약 응답 구조화 + Discord 임베드 디자인 개선
- 요약을 JSON으로 구조화: oneliner, main_points, conclusion 분리
- Claude에게 JSON 형식으로만 응답하도록 프롬프트 변경
- n8n Discord 임베드: 섹션별 필드 분리, 이모지, 타임스탬프 추가
- JSON.stringify Expression으로 특수문자 이스케이프 처리
- 전체 문서 API 응답 형식 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 17:18:31 +09:00

9.6 KiB
Raw Blame History

n8n 워크플로우 구성 가이드

워크플로우 개요

이 워크플로우는 2개의 YouTube 채널(머니코믹스, 슈카월드)의 새 영상을 YouTube Data API v3로 감지하고, FastAPI /api/news/summarize 엔드포인트를 호출하여 요약한 뒤, 응답 결과에 따라 Discord로 요약 또는 에러 알림을 전송하는 자동화 파이프라인입니다.

전체 흐름:

Schedule Trigger (매 정시)
  ├→ HTTP Request (머니코믹스 playlistItems) → Edit Fields (channel_name: 머니코믹스)
  └→ HTTP Request (슈카월드 playlistItems)   → Edit Fields (channel_name: 슈카월드)
                                                          ↓
                                                    Merge (Append)
                                                          ↓
                                              Code (새 영상만 필터링)
                                                          ↓
                                HTTP Request (POST /api/news/summarize)
                                                          ↓
                                                  Switch (status 분기)
                                         ├→ ok → Discord 요약 전송
                                         ├→ skipped → No Operation
                                         └→ error → Discord 에러 알림

사전 준비: YouTube Data API 키 발급

  1. Google Cloud Console → 프로젝트 생성/선택
  2. "YouTube Data API v3" 검색 → 사용 설정
  3. 사용자 인증 정보 → API 키 생성
  4. (권장) 키 제한 설정:
    • API 제한: YouTube Data API v3만 허용
    • 애플리케이션 제한: IP 주소 → n8n 서버 IP
  5. 일일 쿼터: 10,000 units (playlistItems.list는 1 unit/요청 → 2채널 × 24회 = 48 units/일)

노드 구성 상세

1. Schedule Trigger

설정 항목
Type Schedule Trigger
Rule Every Hour
Minute 0

2. YouTube API — playlistItems (채널별 각 1개)

설정 항목
Type HTTP Request
Method GET
URL https://www.googleapis.com/youtube/v3/playlistItems
Send Query Parameters ON
Specify Query Parameters Using Individual Fields

Query Parameters (Add Parameter로 추가):

파라미터
part snippet
playlistId 채널 업로드 목록 ID (아래 참고)
maxResults 5
key YouTube Data API v3 키

채널별 playlistId (채널 ID의 UCUU로 변환):

채널 채널 ID playlistId (업로드 목록)
머니코믹스 UCJo6G1u0e_-wS-JQn3T-zEw UUJo6G1u0e_-wS-JQn3T-zEw
슈카월드 UCsJ6RuBiTVWRX156FVbeaGg UUsJ6RuBiTVWRX156FVbeaGg

응답 예시:

{
  "items": [
    {
      "snippet": {
        "publishedAt": "2026-03-25T06:00:00Z",
        "title": "영상 제목",
        "resourceId": { "videoId": "abc123" }
      }
    }
  ]
}

3. Edit Fields (채널별 각 1개)

설정 항목
Type Edit Fields (Set)
Mode Manual Mapping
Fields to Set channel_name (String) = 머니코믹스 또는 슈카월드
Include Other Input Fields All (기존 데이터 유지)

4. Merge

설정 항목
Type Merge
Mode Append

5. Code (새 영상 필터링)

설정 항목
Type Code
Language JavaScript
Mode Run Once for All Items

최근 1시간 이내 발행된 영상만 필터링합니다:

const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);

return $input.all().filter(item => {
  const items = item.json.items || [];
  return items.some(i => new Date(i.snippet.publishedAt) > oneHourAgo);
}).flatMap(item => {
  const channelName = item.json.channel_name || '';
  return (item.json.items || [])
    .filter(i => new Date(i.snippet.publishedAt) > oneHourAgo)
    .map(i => ({
      json: {
        video_url: `https://www.youtube.com/watch?v=${i.snippet.resourceId.videoId}`,
        title: i.snippet.title,
        channel_name: channelName,
      }
    }));
});

참고: Schedule Trigger가 매 정시 실행되므로, 1시간 이내 영상을 필터링하면 새 영상만 처리됩니다. 영상이 없으면 빈 배열이 반환되어 이후 노드가 실행되지 않습니다.

6. HTTP Request (요약 API 호출)

설정 항목
Type HTTP Request
Method POST
URL https://<서버주소>/api/news/summarize
Send Body ON
Body Content Type JSON
Specify Body Using JSON

Body (Expression 모드):

{
  "video_url": "{{ $json.video_url }}",
  "title": "{{ $json.title }}",
  "channel_name": "{{ $json.channel_name }}"
}

Headers (API_SECRET 사용 시):

헤더
X-Api-Secret 설정한 시크릿 값

Options:

  • Always Output Data: ON (에러 시에도 다음 노드로 전달)

주의: 영상 제목에 큰따옴표(")가 포함된 경우 JSON이 깨질 수 있습니다. 반드시 Expression 모드를 사용하세요.

7. Switch (응답 분기)

설정 항목
Type Switch
Mode Rules
Data Type String
Value {{ $json.status }}

Rules:

Rule Operation Value Output
Rule 1 Equal ok → Discord 요약 전송
Rule 2 Equal skipped → No Operation (무시)
Rule 3 Equal error → Discord 에러 알림

8. Discord 요약 전송 (status=ok)

설정 항목
Type HTTP Request
Method POST
URL Discord 웹훅 URL
Send Body ON
Body Content Type JSON
Specify Body Using JSON

Body:

Specify Body를 Using JSON으로 선택 후, JSON 입력란 우측의 Expression 토글을 켜고 아래 코드를 입력합니다. JSON.stringify를 사용해야 요약 텍스트의 특수문자(따옴표, 줄바꿈 등)가 자동 이스케이프됩니다.

={{ JSON.stringify({
  embeds: [{
    title: "📰 [" + $json.channel_name + "] " + $json.title,
    url: $json.video_url,
    description: "### 💡 " + $json.oneliner,
    color: 2829105,
    fields: [
      {
        name: "📋 주요 내용",
        value: $json.main_points,
        inline: false
      },
      {
        name: "🎯 결론 / 시사점",
        value: $json.conclusion,
        inline: false
      },
      {
        name: "🔗 원본 영상",
        value: $json.video_url,
        inline: false
      }
    ],
    thumbnail: {
      url: "https://img.youtube.com/vi/" + $json.video_url.split("v=")[1] + "/hqdefault.jpg"
    },
    footer: {
      text: "YouTube 뉴스 요약 봇 • " + $json.channel_name
    },
    timestamp: new Date().toISOString()
  }]
}) }}

9. Discord 에러 알림 (status=error)

설정 항목
Type HTTP Request
Method POST
URL Discord 웹훅 URL
Send Body ON
Body Content Type JSON
Specify Body Using JSON (Expression 토글 ON)

Body:

={{ JSON.stringify({
  embeds: [{
    title: "❌ [" + $json.channel_name + "] 뉴스 요약 실패",
    color: 15548997,
    fields: [
      { name: "영상 제목", value: $json.title || "(제목 없음)", inline: false },
      { name: "영상 URL", value: $json.video_url, inline: false },
      { name: "에러 타입", value: "`" + $json.error_type + "`", inline: true },
      { name: "에러 내용", value: "```\\n" + $json.error_message + "\\n```", inline: false }
    ],
    footer: {
      text: "봇 요약 처리 에러 — FastAPI 응답"
    },
    timestamp: new Date().toISOString()
  }]
}) }}

에러 처리

에러 알림은 두 종류로 구분됩니다:

구분 발생 위치 원인 예시 알림 제목
워크플로우 에러 n8n 자체 API 연결 불가, 노드 설정 오류, YouTube API 실패 ⚠️ n8n 워크플로우 에러
요약 처리 에러 FastAPI 봇 자막 없음, 쿠키 만료, Claude API 오류 [채널명] 뉴스 요약 실패

Error Trigger 워크플로우 (n8n 워크플로우 에러)

메인 워크플로우 자체가 실패하는 경우를 대비해 별도 에러 워크플로우를 만들 수 있습니다.

  1. 새 워크플로우 생성 → Error Trigger 노드 추가
  2. HTTP Request 노드로 Discord Webhook 호출:
설정 항목
Type HTTP Request
Method POST
URL Discord 웹훅 URL
Send Body ON
Body Content Type JSON
Specify Body Using JSON

Body (Expression 모드):

{
  "embeds": [{
    "title": "⚠️ n8n 워크플로우 에러",
    "color": 16753920,
    "description": "n8n 워크플로우 실행 중 에러가 발생했습니다. 봇 요약 처리가 아닌 **워크플로우 자체 문제**입니다.",
    "fields": [
      { "name": "워크플로우", "value": "{{ $json.workflow.name }}", "inline": true },
      { "name": "실패 노드", "value": "{{ $json.execution.lastNodeExecuted }}", "inline": true },
      { "name": "에러 내용", "value": "```\n{{ $json.execution.error.message || '상세 내용 없음' }}\n```", "inline": false },
      { "name": "실행 ID", "value": "`{{ $json.execution.id }}`", "inline": true }
    ],
    "footer": {
      "text": "n8n Error Trigger — 워크플로우 에러"
    }
  }]
}
  1. 메인 워크플로우의 Settings → Error Workflow에서 이 에러 워크플로우를 지정

참고: $json.execution.error.message가 빈 값일 수 있으므로 || '상세 내용 없음'으로 fallback 처리합니다.